Session Management

How the gateway resolves, rotates, and exposes session state on the server, and how the auth status handler powers authentication checks.

Session state in the module is resolved entirely on the server. Every protected request runs through a token rotation check, a cached user-data lookup against the IAM service, and the verified result is placed on the H3 event context for the handler to read. A pre-built auth status route exposes the same resolution over HTTP so any browser client, or a Vue app, can read the session state with a single request.


Auth status handler

getAuthStatusHandler is a pre-built H3 handler that resolves the current session. It calls ensureValidCredentials to check and rotate tokens, then proxies to the IAM /secret/data endpoint and returns a normalized response.

Register it at any path you choose. In manual H3 and Nitro setups, the common path is /auth/users/authStatus. Under the Nuxt module, the default path is /api/auth/users/authStatus:

// H3 v1
router.get('/auth/users/authStatus', getAuthStatusHandler)

// H3 v2
app.get('/auth/users/authStatus', getAuthStatusHandler)

When using the Nuxt module, this route is registered automatically at the path set by the authStatusUrl module option.

The response shape when authorized:

{
  "authorized": true,
  "userId": 42,
  "roles": ["user"],
  "ipAddress": "1.2.3.4",
  "userAgent": "Mozilla/5.0 ...",
  "date": "2026-04-10T..."
}

When not authorized:

{
  "authorized": false
}

HTTP 202 indicates an MFA challenge and carries a message field with instructions.


Token rotation

ensureValidCredentials runs inside every authenticated handler wrapper before your handler code executes. It is responsible for keeping the session alive by checking token state and rotating when needed.

Before any token logic runs, the function reads the session and canary_id cookies. If either is absent, it throws HTTP 401. The __Secure-a access token cookie is optional at this stage.

Two paths

The function takes one of two paths depending on whether an access token is present.

No access token: the function skips the metadata check and calls POST /auth/user/refresh-session on the IAM service.

Access token present: the function first calls GET /secret/accesstoken/metadata on the IAM service to check the token's state. Results are cached in a MiniCache instance keyed by the access token value (cached for msUntilExp - refreshThreshold - 5 seconds). The metadata result determines what happens next:

Metadata resultAction
Server error, no response, or unexpected statusDelete cache entry, rotate
mfa: true (IAM returned 202)Return { text: 'MFA required' } with HTTP 202, do not rotate
authorized: falseDelete cache entry, rotate
shouldRotate: trueDelete cache entry, rotate
Valid and within thresholdSet event.context.accessToken and event.context.session, continue

If the metadata call throws unexpectedly, the cache entry is deleted and rotation is attempted.

Rotation

When rotation is needed, the function calls POST /auth/user/refresh-session with the session and canary_id cookies. The IAM service response determines the outcome:

IAM statusAction
201Apply new tokens and continue
202Return { text: 'MFA required', message } with HTTP 202, do not apply cookies
401Throw HTTP 401, re-authentication required
429Forward Retry-After header, return rate-limit error
500 or unexpectedThrow HTTP 500

On a successful 201 response, applyRotationResult writes the new __Secure-a, a-iat cookies and forwards the Set-Cookie headers from the IAM response (which contain the new session cookie). It also sets event.context.accessToken, event.context.session, and event.context.isRotated = true.

Deduplication

Both the metadata check and the rotation call are wrapped with lockAsyncAction keyed on the session refresh token value. If two requests for the same session arrive simultaneously, only one call proceeds at each stage. The second request waits and reuses the result.

Accessing token metadata directly

getAccessTokenMetaData reads the access token from the request cookies and calls the IAM /secret/accesstoken/metadata endpoint. Results are cached in a MiniCache instance:

const meta = await getAccessTokenMetaData(event)

// meta.msUntilExp   - milliseconds until the token expires
// meta.shouldRotate - true when rotation threshold is reached
// meta.authorized   - false when the token is invalid or absent

getCachedUserData calls the IAM /secret/data endpoint and caches the result in the configured unstorage instance. The cache key is a SHA-256 hash of the canary ID, refresh token, and access token. Cached results are served for the duration of successTtl (default 30 days) without a network call:

const result = await getCachedUserData(event, cookies, token, storage)

if (result.type === 'SUCCESS') {
  const { userId, roles, authorized } = result.data
}

if (result.type === 'ERROR') {
  // result.reason: 'UNAUTHORIZED' | 'MFA' | 'RATE_LIMIT' | 'SERVER_ERROR' | ...
  // result.status: number
}

getCachedUserData is called internally by defineAuthenticatedEventHandler and defineOptionalAuthenticationEvent. Call it directly only when you need user data outside of a handler wrapper context.


Reading session state from the H3 event context

Inside any authenticated handler wrapper, event.context.authorizedData holds the verified session data after the auth check passes:

export default defineAuthenticatedEventHandler(async (event) => {
  const { userId, roles, authorized } = event.context.authorizedData
  return { userId }
})

The authorizedData object matches the ServerResponse type returned by the IAM service:

interface ServerResponse {
  authorized: boolean
  userId?: string
  roles?: string[] | string
  ipAddress: string
  userAgent: string
  date: string
  reason?: string
  error?: string
  message?: string
}

See Route Protection for the full set of handler wrappers and when to use each one.


Reading session state on the client

The auth-h3client/client entry point ships a browser-side Vue composable, useAuthData, that calls the auth status route during SSR, stores the result in a singleton, and hydrates it on the client without triggering a second request. The composable works in any Nuxt or Vue application that has access to the nuxt/app composables. See the Client-side section for the full browser-side surface.

Logo