Skip to main content

Sessions

GuildGate sends an opaque session token to the browser and stores only its hash. The CSRF token is bound to the same session identity.

Creating a session​

const created = await gate.createSession({
userId: discordUser.id,
locale: "tr",
metadata: {
loginMethod: "discord",
},
});

reply.header("Set-Cookie", created.setCookie);

The result contains:

  • session: the record written to the store
  • sessionToken: the opaque value sent only to the client
  • csrfToken: the token used by state-changing requests
  • setCookie: the secure cookie header

Time limits​

security.session contains four bounds:

session: {
ttlMs: 7 * 24 * 60 * 60_000,
idleTimeoutMs: 30 * 60_000,
rotateAfterMs: 15 * 60_000,
maximumSessionsPerUser: 5,
}
  • ttlMs: absolute expiry
  • idleTimeoutMs: expiry after inactivity
  • rotateAfterMs: token rotation point
  • maximumSessionsPerUser: active-session cap for one user

Do not rely only on cookie expiry; validate the store record.

Atomic per-user cap​

In 1.1.0, the official memory, Redis, and PostgreSQL stores insert a session and evict excess sessions inside one store operation.

Implement this signature for a shared custom store:

interface SessionStore {
create?(record: SessionRecord, maximumSessionsPerUser: number): Promise<void>;
}

Calling listByUser() followed by separate delete() operations creates a race in multi-instance deployments. The kernel keeps a compatibility path when create() is absent, but an atomic implementation is preferred for production stores.

Rotation​

When the session reaches its rotation threshold, action metadata may contain replacement session and CSRF tokens. The HTTP adapter must write the replacement cookie and the client must use the new CSRF value.

Do not log the old token or persist it in frontend state. The session cookie should be HttpOnly.

Revocation​

await gate.revokeSession(sessionToken);
const revokedCount = await gate.revokeUserSessions(userId);

Revoking all sessions for a user is useful after account-security events, owner intervention, or OAuth credential changes.

Use a revocation bus for realtime transports so open connections for the same user close across workers.

The production default uses __Host-guildgate.sid. A __Host- cookie requires:

  • Secure
  • Path=/
  • no domain attribute

SameSite may be Lax or Strict. Test the OAuth return and application routes before selecting it.

In 1.1.0, an invalid session resolution adds clearSessionCookie metadata. The official Fastify, Express, and Hono adapters send a cookie with Max-Age=0.

A custom adapter that ignores this metadata causes the browser to keep sending an invalid cookie on every request.

Production checks​

  • Is the session token transported only over HTTPS?
  • Does the store contain any raw token?
  • Is the Redis or PostgreSQL session cap atomic?
  • Is a rotation result written to the correct cookie?
  • Do logout, owner revocation, and expiry close realtime connections?
  • Does audit use sessionIdHash rather than the token?