Session Management
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 result | Action |
|---|---|
| Server error, no response, or unexpected status | Delete cache entry, rotate |
mfa: true (IAM returned 202) | Return { text: 'MFA required' } with HTTP 202, do not rotate |
authorized: false | Delete cache entry, rotate |
shouldRotate: true | Delete cache entry, rotate |
| Valid and within threshold | Set 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 status | Action |
|---|---|
201 | Apply new tokens and continue |
202 | Return { text: 'MFA required', message } with HTTP 202, do not apply cookies |
401 | Throw HTTP 401, re-authentication required |
429 | Forward Retry-After header, return rate-limit error |
500 or unexpected | Throw 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.