Cookies
The module uses a fixed set of cookies to manage session state. Each cookie has specific security attributes that enforce origin binding, transport security, and script isolation.
Cookie inventory
| Name | Content | HttpOnly | SameSite | Secure | Set by |
|---|---|---|---|---|---|
__Secure-a | Access token | Yes | Strict | Yes | Login, OAuth callback, token rotation |
a-iat | Access token issued-at timestamp | Yes | Strict | Yes | Login, OAuth callback, token rotation |
session | Refresh token | Yes | Strict | Yes | IAM service (forwarded) |
__Host-csrf | Signed CSRF token | No | Strict | Yes | generateCsrfCookie |
__Host-dr_i_n | Signed visitor cookie (skips bot check) | Yes | Strict | Yes | botDetectorMiddleware / checkForBots() |
canary_id | Visitor fingerprint identifier | Yes | Strict | Yes | IAM service (forwarded) |
state{name} | OAuth state (signed) | Yes | Lax or None | Yes | OAuthRedirect (3 min TTL) |
pkce_v{name} | PKCE verifier | Yes | Lax or None | Yes | OAuthRedirect (3 min TTL) |
nonce{name} | OIDC nonce | Yes | Lax or None | Yes | OAuthRedirect (3 min TTL) |
The __Secure- prefix requires Secure: true. The __Host- prefix additionally requires Path: / and no Domain attribute, meaning the cookie is scoped to the exact origin with no subdomain access.
OAuth flow cookies (state{name}, pkce_v{name}, nonce{name}) use SameSite: None for providers that use response_mode: form_post, and SameSite: Lax otherwise.
makeCookie
makeCookie(event, name, value, options) sets an HTTP cookie on the response. It is a thin wrapper around the H3 setCookie function with typed options.
makeCookie(event, '__Secure-custom', value, {
httpOnly: true,
sameSite: 'strict',
secure: true,
path: '/',
domain: domain,
maxAge: 900 // 15 minutes
})
Use makeCookie in custom handlers whenever you need to set a cookie with the same attribute pattern as the session cookies. Pass maxAge in seconds.
Signed cookies
The module uses HMAC-signed cookies for the CSRF token and OAuth state. The signed format is:
base64(value).base64(keyword).expiry.hmac
Two functions manage this format:
createSignedValue(raw, ttlMs, keyword) creates a signed cookie string. The keyword binds the signature to a specific context so that a signed value from one context cannot be replayed in another.
verifySignedValue(cookie, keyword) parses and verifies the signature and expiry. It returns { valid: boolean, payload: { value, exp } }.
const signed = createSignedValue('my-token', 1000 * 60 * 5, 'my-context')
makeCookie(event, '__Secure-custom', signed, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 300
})
const { valid, payload } = verifySignedValue(cookieValue, 'my-context')
if (!valid || !payload) {
throw createError({ statusCode: 403 })
}
const rawToken = payload.value
The cryptoCookiesSecret field in the configuration is the HMAC key used for all signed cookies. It should be at least 32 bytes of random data and must remain stable across application restarts.
Cookie domain and access token TTL
The __Secure-a access token cookie and a-iat issued-at cookie are always set with the domain and maxAge values fetched from getOperationalConfig. That function calls the IAM service at GET /operational/config, parses the response, and caches the result for 24 hours keyed by the IAM server address. The cache means the IAM is called at most once per process lifetime for this data.
Two values come from that response:
domain: the cookie domain configured on the IAM service. Used as theDomainattribute on__Secure-aanda-iat, scoping them across subdomains of that domain.accessTokenTTL: the IAM returns this value in milliseconds. The gateway converts it to seconds withMath.floor(ms / 1000)and uses it as theMaxAgeof both cookies.
Clearing cookies on logout
logoutHandler deletes all session cookies explicitly by calling deleteCookie with matching attributes. The cookie deletion must use the same domain, path, secure, httpOnly, and sameSite values as the original Set-Cookie call. Using makeCookie with an empty value and maxAge: 0 achieves the same result for custom flows.