diff --git a/go.mod b/go.mod
index b616bbf11cb783bee992bb42239e36b17078fd88..2b6beb7e0adda5efe77c211d9e606fac2435c405 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	git.autistici.org/ai3/go-common v0.0.0-20221125154433-06304016b1da
 	git.autistici.org/id/auth v0.0.0-20221218082828-0c11710e98c8
 	git.autistici.org/id/go-sso v0.0.0-20221216110623-a98dfc78fec5
-	git.autistici.org/id/keystore v0.0.0-20220913090155-3a59b1e00032
+	git.autistici.org/id/keystore v0.0.0-20221219193813-81c2463e8b40
 	git.autistici.org/id/usermetadb v0.0.0-20221125171152-3bbb63732147
 	github.com/crewjam/saml v0.4.10
 	github.com/duo-labs/webauthn v0.0.0-20220330035159-03696f3d4499
diff --git a/go.sum b/go.sum
index cf87146a201633ebbb94e827af90ce3804449513..0733bbb976638705f933a49d1725fa3b7b3b290f 100644
--- a/go.sum
+++ b/go.sum
@@ -54,18 +54,14 @@ contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EU
 contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
 contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-git.autistici.org/ai3/go-common v0.0.0-20220912095004-9a984189694c/go.mod h1:E//kc9AgsYrMeaF/eKqNxESnQHEW4Dif0sODQA3Osi4=
 git.autistici.org/ai3/go-common v0.0.0-20221125154433-06304016b1da h1:fizdAjFv2vWz+83IoeRW2L0Shyo3dDquXyQKWRGs4jc=
 git.autistici.org/ai3/go-common v0.0.0-20221125154433-06304016b1da/go.mod h1:FTGqOGPpuoFg7TiHshYCyp5j1Ab3ek0J0KcS++vEjxw=
-git.autistici.org/id/auth v0.0.0-20221125183052-116b375abf7e h1:jn6cK7sn3VmPAm+VxTZr4UAAiW8p3W13FqGpXI/YZxE=
-git.autistici.org/id/auth v0.0.0-20221125183052-116b375abf7e/go.mod h1:oQiTwcCfJJD4/DgkpimsLuFxwmWdnrFONNu6DyLyupk=
 git.autistici.org/id/auth v0.0.0-20221218082828-0c11710e98c8 h1:L9sqyIKNaK4QhqUwKC4xO3IKnHEVFFFzWai2c4wuYe0=
 git.autistici.org/id/auth v0.0.0-20221218082828-0c11710e98c8/go.mod h1:D6nxD4DllapFwglkkrHBsj24g6ZccmWBsEOZ2XZbksQ=
-git.autistici.org/id/go-sso v0.0.0-20220830192001-8a1845d61e91/go.mod h1:n3YNIlKKfYWYqPGPLh4KDT1QpOVqoSp8w3l4DBR/oXk=
 git.autistici.org/id/go-sso v0.0.0-20221216110623-a98dfc78fec5 h1:F9uvX2uW9QNgna/OeG2EnLZpWvrz0ZNg82nvPX4M9ns=
 git.autistici.org/id/go-sso v0.0.0-20221216110623-a98dfc78fec5/go.mod h1:n3YNIlKKfYWYqPGPLh4KDT1QpOVqoSp8w3l4DBR/oXk=
-git.autistici.org/id/keystore v0.0.0-20220913090155-3a59b1e00032 h1:nbA1DQgSc60nd4bL5x4CStDEfEILsQv0ZjAbsNjvshM=
-git.autistici.org/id/keystore v0.0.0-20220913090155-3a59b1e00032/go.mod h1:NrU8Q2sBwY/X9FwFKMDAd4hI2c4jGnGB9kJPMxDrRz0=
+git.autistici.org/id/keystore v0.0.0-20221219193813-81c2463e8b40 h1:w6Jbqs8x9vKowhr5z39LLk2Cduxigkdl04NIEtjBLFg=
+git.autistici.org/id/keystore v0.0.0-20221219193813-81c2463e8b40/go.mod h1:bO3Br4enMkeBfWfS3wnowhwa6q581JyUY4Rd6NMctnM=
 git.autistici.org/id/usermetadb v0.0.0-20221125171152-3bbb63732147 h1:85DqqCXrQc6BtF9yqG5chRe5hvKdsRDK6SeDhYYqeCY=
 git.autistici.org/id/usermetadb v0.0.0-20221125171152-3bbb63732147/go.mod h1:g8Kfp0/rGyaY7ArD35K1HB2MN/gm+vfQ3/aNS/fkWH8=
 github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU=
@@ -307,6 +303,7 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -594,7 +591,6 @@ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC
 github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -700,7 +696,6 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
 github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
@@ -1104,7 +1099,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/login/login_test.go b/login/login_test.go
index 7623abc2969b0445ce0537575253d06448332599..fb321a9aefbf9da222befe4aec3e5538b2c1b7e0 100644
--- a/login/login_test.go
+++ b/login/login_test.go
@@ -37,9 +37,15 @@ func validWebAuthnRequest(req *auth.Request) bool {
 	return req.WebAuthnSession != nil && req.WebAuthnSession.Challenge == fakeWebauthnSession.Challenge
 }
 
-type fakeAuthClient struct{}
+type fakeAuthClient struct {
+	sessionID string
+}
+
+func (c *fakeAuthClient) Authenticate(ctx context.Context, req *auth.Request) (*auth.Response, error) {
+	if sid, ok := GetSessionID(ctx); ok {
+		c.sessionID = sid
+	}
 
-func (c *fakeAuthClient) Authenticate(_ context.Context, req *auth.Request) (*auth.Response, error) {
 	p := string(req.Password)
 	info := &auth.UserInfo{
 		Shard:  "shard1",
@@ -203,7 +209,7 @@ func TestTestPages(t *testing.T) {
 }
 
 // Start a test server, with the login app wrapping a test application.
-func startServer(t *testing.T) *httptest.Server {
+func startServer(t *testing.T) (*httptest.Server, *fakeAuthClient) {
 	devMgr, err := device.New(&device.Config{
 		AuthKey: string(securecookie.GenerateRandomKey(32)),
 	}, "")
@@ -237,7 +243,7 @@ func startServer(t *testing.T) *httptest.Server {
 	}
 	// h doubles as staticHandler.
 	wh := doWrap(h, devMgr, ac, h, renderer, urls, &config)
-	return httptest.NewServer(wh)
+	return httptest.NewServer(wh), ac
 }
 
 type step struct {
@@ -247,26 +253,30 @@ type step struct {
 	expectErr   bool
 }
 
+func runStep(t *testing.T, c *httpClient, idx int, step step) {
+	data, uri, err := step.requestFunc(c)
+	if err != nil && !step.expectErr {
+		t.Fatalf("error at step %d: unexpected error: %v", idx, err)
+	}
+	if err == nil && step.expectErr {
+		t.Fatalf("error at step %d: was expecting an error but did not get one", idx)
+	}
+	if step.expectedURI != "" && step.expectedURI != uri {
+		t.Errorf("error at step %d: bad response URI, got '%s' expected '%s'", idx, uri, step.expectedURI)
+	}
+	if step.checkFunc != nil && !step.checkFunc(data) {
+		t.Fatalf("error at step %d: bad response '%s'", idx, data)
+	}
+}
+
 func runConversation(t *testing.T, steps ...step) {
-	srv := startServer(t)
+	srv, _ := startServer(t)
 	defer srv.Close()
 
 	c := newClient(srv.URL)
 
 	for idx, step := range steps {
-		data, uri, err := step.requestFunc(c)
-		if err != nil && !step.expectErr {
-			t.Fatalf("error at step %d: unexpected error: %v", idx, err)
-		}
-		if err == nil && step.expectErr {
-			t.Fatalf("error at step %d: was expecting an error but did not get one", idx)
-		}
-		if step.expectedURI != "" && step.expectedURI != uri {
-			t.Errorf("error at step %d: bad response URI, got '%s' expected '%s'", idx, uri, step.expectedURI)
-		}
-		if step.checkFunc != nil && !step.checkFunc(data) {
-			t.Fatalf("error at step %d: bad response '%s'", idx, data)
-		}
+		runStep(t, c, idx, step)
 	}
 }
 
@@ -459,6 +469,25 @@ func TestLogin_2FA_Switch_Ok(t *testing.T) {
 	)
 }
 
+func TestLogin_SessionID_Is_Set(t *testing.T) {
+	srv, ac := startServer(t)
+	defer srv.Close()
+
+	c := newClient(srv.URL)
+
+	steps := []step{
+		requestSiteStep(),
+		loginOKStep(),
+	}
+	for idx, step := range steps {
+		runStep(t, c, idx, step)
+	}
+
+	if ac.sessionID == "" {
+		t.Fatal("session ID was not set during Authenticate() call")
+	}
+}
+
 var testCredentialRequestBody = `{
 	"id":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
 	"rawId":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
diff --git a/login/logout.go b/login/logout.go
index 91245c2b13c7ce3a26e4cb547a10316477eabdf7..74ae4a52b8c00a908617cd33c3c6af3a36db13af 100644
--- a/login/logout.go
+++ b/login/logout.go
@@ -24,7 +24,8 @@ func newLogoutHandler(authClient AuthClient, renderer *common.Renderer) http.Han
 		// If the user is autenticated, invoke the Logout
 		// method on the authentication client.
 		if auth, ok := GetAuth(req.Context()); ok && auth.check() == nil {
-			if err := authClient.Logout(req.Context(), auth.Username, auth.UserInfo); err != nil {
+			ctx := withSessionID(req.Context(), auth.SessionID)
+			if err := authClient.Logout(ctx, auth.Username, auth.UserInfo); err != nil {
 				log.Printf("logout error for %s: %v", auth.Username, err)
 			}
 		}
diff --git a/login/middleware.go b/login/middleware.go
index 697c0301fdf220ef8c822c462419a4b5443ae841..18fc5b95753e4311082546582eac0f77deee64d5 100644
--- a/login/middleware.go
+++ b/login/middleware.go
@@ -17,6 +17,9 @@ type Auth struct {
 	Username string         `json:"u"`
 	UserInfo *auth.UserInfo `json:"ui"`
 
+	// Sticky session ID.
+	SessionID string `json:"sid"`
+
 	// Deadline until authentication will need to be renewed. The
 	// securecookie also provides a similar expiration mechanism,
 	// but we do not use it here because we want to be able to
diff --git a/login/session_id.go b/login/session_id.go
new file mode 100644
index 0000000000000000000000000000000000000000..abb57d13ff9df0dce308611ca83491f696ca1fac
--- /dev/null
+++ b/login/session_id.go
@@ -0,0 +1,29 @@
+package login
+
+import (
+	"context"
+	"encoding/base32"
+
+	"git.autistici.org/id/sso-server/common"
+)
+
+func newSessionID() string {
+	// Session IDs just need to be unique per user, so 80 bits of
+	// randomness are going to be plenty (also it makes for nice
+	// base32 without padding).
+	b := common.RandomBytes(10)
+	return base32.StdEncoding.EncodeToString(b)
+}
+
+const sessCtxKey ctxKey = 1
+
+// GetSessionID retrieves the session ID which is available during the
+// AuthClient.Authenticate call.
+func GetSessionID(ctx context.Context) (string, bool) {
+	s, ok := ctx.Value(sessCtxKey).(string)
+	return s, ok
+}
+
+func withSessionID(ctx context.Context, sessionID string) context.Context {
+	return context.WithValue(ctx, sessCtxKey, sessionID)
+}
diff --git a/login/session.go b/login/session_store.go
similarity index 100%
rename from login/session.go
rename to login/session_store.go
diff --git a/login/state.go b/login/state.go
index 6673fcbb9458e2ed0f0eb50aa02468653022174e..e6de52a6e22b7ba77862f292081a23efb5092aec 100644
--- a/login/state.go
+++ b/login/state.go
@@ -113,6 +113,8 @@ type loginState struct {
 	Password     string         `json:"p"`
 	AuthResponse *auth.Response `json:"ar"`
 
+	SessionID string `json:"sid"`
+
 	store *sessionStore
 }
 
@@ -167,14 +169,17 @@ func (l *loginState) process(req *http.Request, ac AuthClient, deviceInfo *userm
 	if err != nil {
 		return err
 	}
-	authResp, err := ac.Authenticate(req.Context(), authReq)
+
+	authResp, err := ac.Authenticate(
+		withSessionID(req.Context(), l.SessionID), authReq)
 	if err != nil {
 		return err
 	}
 
 	log.Printf(
-		"authentication request: %s -> %s",
+		"authentication request: %s,sid=%s -> %s",
 		authRequestDebugString(authReq),
+		l.SessionID,
 		authResponseDebugString(authResp),
 	)
 
@@ -298,6 +303,9 @@ func (e *loginEngine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 		state.State = StateBEGIN
 	}
 	state.store = e.loginSessionStore
+	if state.SessionID == "" {
+		state.SessionID = newSessionID()
+	}
 
 	// If we're supposed to handle this page, ensure that the
 	// state and URL match, or redirect the user back to the
@@ -336,9 +344,10 @@ func (e *loginEngine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 		// Create a new Auth session cookie, delete the
 		// loginSession.
 		auth := Auth{
-			Username: state.Username,
-			UserInfo: state.AuthResponse.UserInfo,
-			Deadline: time.Now().Add(e.authTTL),
+			Username:  state.Username,
+			UserInfo:  state.AuthResponse.UserInfo,
+			SessionID: state.SessionID,
+			Deadline:  time.Now().Add(e.authTTL),
 		}
 		if err := e.authSessionStore.setSession(w, &auth); err != nil {
 			goto fail
diff --git a/server/auth.go b/server/auth.go
index 0b6fbc5061b7e30408b2b9b7a0e649442a9dec68..c4b2148725582f0ea42ade3fee22c097d85bab9b 100644
--- a/server/auth.go
+++ b/server/auth.go
@@ -68,7 +68,11 @@ func (k *keystoreAuthClient) Authenticate(ctx context.Context, req *auth.Request
 
 			// Add 10 minutes of buffer to the auth session TTL.
 			ttl := k.authTTLSecs + 600
-			err = k.keystore.Open(ctx, shard, req.Username, string(req.Password), ttl)
+
+			// Fetch the session ID from context.
+			sessionID, _ := login.GetSessionID(ctx)
+
+			err = k.keystore.Open(ctx, shard, req.Username, string(req.Password), sessionID, ttl)
 
 			if err == nil {
 				keystoreCounter.WithLabelValues("ok", shard).Inc()
@@ -90,7 +94,9 @@ func (k *keystoreAuthClient) Logout(ctx context.Context, username string, info *
 			shard = info.Shard
 		}
 
-		if kerr := k.keystore.Close(ctx, shard, username); kerr != nil {
+		sessionID, _ := login.GetSessionID(ctx)
+
+		if kerr := k.keystore.Close(ctx, shard, username, sessionID); kerr != nil {
 			// This is not a fatal error.
 			log.Printf("warning: failed to wipe keystore for user %s: %v", username, err)
 		}
@@ -109,9 +115,13 @@ type delayedAuthClient struct {
 func withRandomDelay(wrap login.AuthClient, delayMs float64) login.AuthClient {
 	return &delayedAuthClient{
 		AuthClient: wrap,
-		rnd:        rand.New(rand.NewSource(newSeed())),
 		delayBase:  delayMs,
 		delayRange: delayMs,
+
+		// We're fine with a weak RNG here, it's just used to
+		// randomize delays a little bit.
+		// nolint: gosec
+		rnd: rand.New(rand.NewSource(newSeed())),
 	}
 }
 
diff --git a/server/http.go b/server/http.go
index ca0b0db824fc863a1adfc9e518b3290ffa70af1a..ee54c039d7c5b7c47ce40105726de213b4b43550 100644
--- a/server/http.go
+++ b/server/http.go
@@ -1,6 +1,7 @@
 package server
 
 import (
+	"errors"
 	"fmt"
 	"io"
 	"log"
@@ -179,7 +180,7 @@ func (h *httpServer) handleExchange(w http.ResponseWriter, req *http.Request) {
 
 	token, err := h.loginService.Exchange(curToken, curService, curNonce, newService, newNonce)
 	switch {
-	case err == ErrUnauthorized:
+	case errors.Is(err, ErrUnauthorized):
 		log.Printf("unauthorized exchange request (%s -> %s)", curService, newService)
 		http.Error(w, "Forbidden", http.StatusForbidden)
 		errorsCounter.WithLabelValues(newService, "true").Inc()
diff --git a/vendor/git.autistici.org/id/keystore/client/client.go b/vendor/git.autistici.org/id/keystore/client/client.go
index 5ffa395150d5a08dc2da72b50a630a0bdaca71d8..c79c747c7c250c5ecd5468af2d3f69b12c5d06e1 100644
--- a/vendor/git.autistici.org/id/keystore/client/client.go
+++ b/vendor/git.autistici.org/id/keystore/client/client.go
@@ -14,9 +14,9 @@ var ErrNoKeys = errors.New("no keys available")
 
 // Client for the keystore API.
 type Client interface {
-	Open(context.Context, string, string, string, int) error
+	Open(context.Context, string, string, string, string, int) error
 	Get(context.Context, string, string, string) ([]byte, error)
-	Close(context.Context, string, string) error
+	Close(context.Context, string, string, string) error
 }
 
 type ksClient struct {
@@ -32,11 +32,12 @@ func New(config *clientutil.BackendConfig) (Client, error) {
 	return &ksClient{be}, nil
 }
 
-func (c *ksClient) Open(ctx context.Context, shard, username, password string, ttl int) error {
+func (c *ksClient) Open(ctx context.Context, shard, username, password, sessionID string, ttl int) error {
 	req := keystore.OpenRequest{
-		Username: username,
-		Password: password,
-		TTL:      ttl,
+		Username:  username,
+		Password:  password,
+		TTL:       ttl,
+		SessionID: sessionID,
 	}
 	var resp keystore.OpenResponse
 	return c.be.Call(ctx, shard, "/api/open", &req, &resp)
@@ -58,9 +59,10 @@ func (c *ksClient) Get(ctx context.Context, shard, username, ssoTicket string) (
 	return resp.Key, err
 }
 
-func (c *ksClient) Close(ctx context.Context, shard, username string) error {
+func (c *ksClient) Close(ctx context.Context, shard, username, sessionID string) error {
 	req := keystore.CloseRequest{
-		Username: username,
+		Username:  username,
+		SessionID: sessionID,
 	}
 	var resp keystore.CloseResponse
 	return c.be.Call(ctx, shard, "/api/close", &req, &resp)
diff --git a/vendor/git.autistici.org/id/keystore/go.mod b/vendor/git.autistici.org/id/keystore/go.mod
index e9aea0d49f55e9161fc947db67f9945c0bc99c0c..4333e56e3650626017bc4f3e50c7ff9a49f6c276 100644
--- a/vendor/git.autistici.org/id/keystore/go.mod
+++ b/vendor/git.autistici.org/id/keystore/go.mod
@@ -3,13 +3,13 @@ module git.autistici.org/id/keystore
 go 1.15
 
 require (
-	git.autistici.org/ai3/go-common v0.0.0-20220912095004-9a984189694c
-	git.autistici.org/id/go-sso v0.0.0-20220830192001-8a1845d61e91
-	github.com/coreos/go-systemd/v22 v22.3.2
+	git.autistici.org/ai3/go-common v0.0.0-20221125154433-06304016b1da
+	git.autistici.org/id/go-sso v0.0.0-20221216110623-a98dfc78fec5
+	github.com/coreos/go-systemd/v22 v22.5.0
 	github.com/go-ldap/ldap/v3 v3.4.4
-	github.com/go-sql-driver/mysql v1.6.0
-	github.com/lib/pq v1.10.1
-	github.com/mattn/go-sqlite3 v1.14.15
+	github.com/go-sql-driver/mysql v1.7.0
+	github.com/lib/pq v1.10.7
+	github.com/mattn/go-sqlite3 v1.14.16
 	github.com/prometheus/client_golang v1.12.2
 	golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
 	gopkg.in/yaml.v3 v3.0.1
diff --git a/vendor/git.autistici.org/id/keystore/go.sum b/vendor/git.autistici.org/id/keystore/go.sum
index 36af8507268cd041debf03142489ca33c3bff34b..714c2c242b86b9cef9d3cb1c520e0854921c3aae 100644
--- a/vendor/git.autistici.org/id/keystore/go.sum
+++ b/vendor/git.autistici.org/id/keystore/go.sum
@@ -62,6 +62,8 @@ git.autistici.org/ai3/go-common v0.0.0-20210308183328-6c663e9176af h1:K9hC03ykUh
 git.autistici.org/ai3/go-common v0.0.0-20210308183328-6c663e9176af/go.mod h1:XUPDXp9FCAk0wlZx7FtC5iI8kSoDv0P3SjQPB1AQY3M=
 git.autistici.org/ai3/go-common v0.0.0-20220912095004-9a984189694c h1:2pWrPn8FRa4yX4CGq7YRq0MmCOTXl5AhfzwA9Xa7NVk=
 git.autistici.org/ai3/go-common v0.0.0-20220912095004-9a984189694c/go.mod h1:E//kc9AgsYrMeaF/eKqNxESnQHEW4Dif0sODQA3Osi4=
+git.autistici.org/ai3/go-common v0.0.0-20221125154433-06304016b1da h1:fizdAjFv2vWz+83IoeRW2L0Shyo3dDquXyQKWRGs4jc=
+git.autistici.org/ai3/go-common v0.0.0-20221125154433-06304016b1da/go.mod h1:FTGqOGPpuoFg7TiHshYCyp5j1Ab3ek0J0KcS++vEjxw=
 git.autistici.org/id/auth v0.0.0-20210308184238-f16e8b49025a/go.mod h1:hBHeDUrpFwhpP3HUFOglAYNEnrNzf0tN66VJTz+pw5Y=
 git.autistici.org/id/go-sso v0.0.0-20181118174541-ad4e62357912 h1:1amb0pZr7c44TXSpFyb8q4J1+Ie+l7K1hYuXVD4zFrY=
 git.autistici.org/id/go-sso v0.0.0-20181118174541-ad4e62357912/go.mod h1:B9omXX7rw0qgWdBoF4RZnM7clwEVejoAe8oNJWETBZ0=
@@ -69,6 +71,8 @@ git.autistici.org/id/go-sso v0.0.0-20210308195111-62a3d97a1dda h1:fm4JdtGnKnxWc3
 git.autistici.org/id/go-sso v0.0.0-20210308195111-62a3d97a1dda/go.mod h1:yFI1gjmj3M/Hq7otoaf1xFGlD5SfUe8GWABcwRhi/ls=
 git.autistici.org/id/go-sso v0.0.0-20220830192001-8a1845d61e91 h1:cTmoy4T1EiTPDxfo+GWwCMFuh+PrlZCxAtlJv1ZbNQE=
 git.autistici.org/id/go-sso v0.0.0-20220830192001-8a1845d61e91/go.mod h1:n3YNIlKKfYWYqPGPLh4KDT1QpOVqoSp8w3l4DBR/oXk=
+git.autistici.org/id/go-sso v0.0.0-20221216110623-a98dfc78fec5 h1:F9uvX2uW9QNgna/OeG2EnLZpWvrz0ZNg82nvPX4M9ns=
+git.autistici.org/id/go-sso v0.0.0-20221216110623-a98dfc78fec5/go.mod h1:n3YNIlKKfYWYqPGPLh4KDT1QpOVqoSp8w3l4DBR/oXk=
 git.autistici.org/id/keystore v0.0.0-20210118071531-7280c2960343/go.mod h1:MGBlG12wf7C0yKtxhaBFomAZX46ZLdgLY9KiXW53IYs=
 git.autistici.org/id/usermetadb v0.0.0-20210308183815-21777c99cbf0/go.mod h1:h8D+U/f9ZoaLtqZDtcPaVDqKCjrhg1OQFgB8zCIv3Sg=
 github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU=
@@ -233,6 +237,8 @@ github.com/coreos/go-systemd/v22 v22.2.0 h1:BBmbNtSc5PuUM3Byxs7yE5rLdxQO4/FMoEXY
 github.com/coreos/go-systemd/v22 v22.2.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
 github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
@@ -353,6 +359,8 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
+github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
@@ -659,6 +667,8 @@ github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
 github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.1 h1:6VXZrLU0jHBYyAqrSPa+MgPfnSvTPuMgK+k0o5kVFWo=
 github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
+github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
@@ -696,6 +706,8 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A
 github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
 github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
+github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -1051,15 +1063,21 @@ go.opentelemetry.io/contrib/propagators/b3 v1.9.0/go.mod h1:fyx3gFXn+4w5uWTTiqaI
 go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM=
 go.opentelemetry.io/otel v1.9.0 h1:8WZNQFIB2a71LnANS9JeyidJKKGOOremcUtb/OtHISw=
 go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo=
+go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4=
+go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ=
 go.opentelemetry.io/otel/exporters/zipkin v1.9.0 h1:06b/nt6xao6th00aue9WU3ZDTTe+InaMXA/vym6pLuA=
 go.opentelemetry.io/otel/exporters/zipkin v1.9.0/go.mod h1:HyIvYIu37wV4Wx5azd7e05x9k/dOz9KB4x0plw2QNvs=
 go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs=
 go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A=
 go.opentelemetry.io/otel/sdk v1.9.0 h1:LNXp1vrr83fNXTHgU8eO89mhzxb/bbWAsHG6fNf3qWo=
 go.opentelemetry.io/otel/sdk v1.9.0/go.mod h1:AEZc8nt5bd2F7BC24J5R0mrjYnpEgYHyTcM/vrSple4=
+go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY=
+go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE=
 go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4=
 go.opentelemetry.io/otel/trace v1.9.0 h1:oZaCNJUjWcg60VXWee8lJKlqhPbXAPB51URuR47pQYc=
 go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo=
+go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E=
+go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -1247,6 +1265,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cO
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A=
 golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/vendor/git.autistici.org/id/keystore/protocol.go b/vendor/git.autistici.org/id/keystore/protocol.go
index fc6113e36dbba6ef1d05775119dbd32e8dabec4f..58b9e3f263b5c8e227d6163cd5778bfa7f523f85 100644
--- a/vendor/git.autistici.org/id/keystore/protocol.go
+++ b/vendor/git.autistici.org/id/keystore/protocol.go
@@ -1,9 +1,10 @@
 package keystore
 
 type OpenRequest struct {
-	Username string `json:"username"`
-	Password string `json:"password"`
-	TTL      int    `json:"ttl"`
+	Username  string `json:"username"`
+	Password  string `json:"password"`
+	TTL       int    `json:"ttl"`
+	SessionID string `json:"session_id"`
 }
 
 type OpenResponse struct{}
@@ -19,7 +20,8 @@ type GetResponse struct {
 }
 
 type CloseRequest struct {
-	Username string `json:"username"`
+	Username  string `json:"username"`
+	SessionID string `json:"session_id"`
 }
 
 type CloseResponse struct{}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 27a7259e5c4691f3a797d0c2ac36a0fcd46114c6..20ced8809744e6453f2ebb8b6d2c0b03fde52db5 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -15,7 +15,7 @@ git.autistici.org/id/auth/lineproto
 ## explicit
 git.autistici.org/id/go-sso
 git.autistici.org/id/go-sso/httpsso
-# git.autistici.org/id/keystore v0.0.0-20220913090155-3a59b1e00032
+# git.autistici.org/id/keystore v0.0.0-20221219193813-81c2463e8b40
 ## explicit
 git.autistici.org/id/keystore
 git.autistici.org/id/keystore/client