Authentication Flow
Complete step-by-step authentication flow from login to token exchange.
Overview
Nuxt Aegis implements a secure multi-stage authentication flow that combines OAuth 2.0, authorization CODEs, and JWT tokens. This page details the complete authentication process from initial login through token refresh.
The stages below describe work performed by the module and the OAuth provider, not implementation steps for your application. Code blocks labeled Application Usage are application-facing examples; request/response contracts describe the module's transport without requiring you to recreate its handlers. Paths shown use the default Aegis endpoints and an application-defined /auth/google provider route.
Complete Authentication Flow
Authentication Stages
Stage 1: OAuth Initiation
The browser visits an application-defined provider route. The module's provider handler builds an authorization URL from the provider configuration and redirects the browser to the OAuth provider.
Application Usage: Login Link
For an application that has configured /auth/google:
<template>
<a href="/auth/google">Login with Google</a>
</template>The provider handler generates an opaque random state and an independent initiator proof. It stores a ten-minute transaction in module-owned memory and sets a per-transaction HttpOnly proof cookie before redirecting. Optional application state and validated local redirectTo are stored as metadata, not encoded in the security state. PKCE is not yet implemented.
Stage 2: OAuth Authorization
The provider authenticates the user and obtains consent. It then redirects the browser to the application's provider route with an OAuth authorization code. This provider-issued code is distinct from the Aegis authorization code created in Stage 4.
Provider Callback Contract
GET /auth/google?code=OAUTH_CODE&state=STATE_VALUEThe exact callback parameters, including whether state is returned, depend on the authorization request and provider.
Stage 3: Token Exchange with Provider
The server exchanges the provider-issued authorization code at the provider's token endpoint, then uses the returned access token to fetch user information. Provider-specific implementations supply the request parameters and extract the user profile.
A provider refresh token, ID token, and expiry metadata may also be returned, depending on the provider and configuration. These are provider tokens, not the application's Aegis tokens. No application-side token exchange implementation is required.
See the OAuth handler implementation for the provider exchange and profile retrieval.
Stage 4: Aegis Authorization CODE
The module invokes configured onUserInfo callbacks to transform the profile, merges any enrichment returned by onUserPersist, and resolves custom claims. Provider-level onUserInfo and customClaims take precedence over their global handler fallbacks. Persistence enrichment does not automatically become custom JWT claims.
The module generates a random Aegis authorization code and stores the profile, provider name, resolved custom claims, and creation and expiration timestamps with it. Provider tokens remain available to configured OAuth callbacks and success hooks during authentication but are not stored in the authorization-code record. The code expires after 60 seconds by default. The server then redirects the browser to the Aegis callback page; it does not create the application's refresh token at this stage.
Browser Redirect Contract
HTTP/1.1 302 Found
Location: /auth/callback?code=AEGIS_CODESee the OAuth handler and authorization code store for implementation details. For application extension points, see the Handlers Guide and Custom Claims Guide.
Stage 5: JWT Token Exchange
The module's AuthCallback.vue page handles /auth/callback. Its onMounted handler reads the Aegis code from the URL and exchanges it via POST /auth/token. You do not need to implement a callback plugin, sign a JWT, or manage the refresh cookie in application code.
The application's refresh token is generated and stored during Stage 5, after POST /auth/token consumes the Aegis authorization CODE from stage 4, and its HttpOnly cookie is set on the token-exchange response. These steps run only when application refresh tokens are enabled; the Aegis refresh token is separate from any refresh token returned by the OAuth provider in Stage 3.
Code-Exchange Request Contract
POST /auth/token
Content-Type: application/json
{ "code": "AEGIS_CODE" }The server retrieves and deletes a valid code before generating application tokens. A missing code produces a 400 response; an invalid, expired, or already-consumed code produces a 401 response. When refresh is enabled, the module creates and stores an Aegis refresh token and sets its HttpOnly cookie on the exchange response.
Successful Response Contract
{
"accessToken": "<JWT>",
"tokenType": "Bearer",
"expiresIn": 3600
}expiresIn is an optional lifetime in seconds; 3600 illustrates a one-hour configuration. The endpoint omits it from JSON when the configured value cannot be converted to seconds. The response has no token, expiresAt, or user field. Refresh tokens are sent in an HttpOnly cookie, not in the JSON body.
The callback page stores accessToken in memory, derives user state from its decoded JWT claims, and redirects to the configured success destination. Application code reads that state through useAuth().user; decoding on the client is not server-side token verification.
See the callback page, token endpoint, and token generation utility for implementation details.
Stage 6: Using $api for Authenticated Requests
After authentication, use the $api plugin to make authenticated API calls:
Application Usage: Authenticated Request
const { $api } = useNuxtApp()
const data = await $api('/api/user/profile')On the client, $api attaches the in-memory token as an Authorization: Bearer <token> header.
Client Refresh Behavior
When automatic refresh is enabled, a 401 response triggers a refresh attempt and, if successful, a retry with the new access token. A 401 does not necessarily mean that the token expired. If refresh fails, the client clears its token and redirects to the configured error destination.
Refresh Request Contract
POST /auth/refresh
Cookie: nuxt-aegis-refresh=<refresh-token>The browser sends the configured HttpOnly refresh cookie automatically; application code does not read its value. The cookie name above is the default.
The successful POST /auth/refresh response has a different shape from the code-exchange response:
{
"success": true,
"message": "Token refreshed successfully",
"accessToken": "<JWT>"
}The client uses accessToken to replace its stored token and update user state. This response does not include tokenType, expiresIn, expiresAt, or a separate user object.
Application Usage: SSR-Aware Data Fetching
During server-side rendering, $api uses a short-lived SSR token:
<script setup lang="ts">
const { $api } = useNuxtApp()
const { data } = await useAsyncData('profile',
() => $api('/api/user/profile')
)
</script>The server $api injects the available SSR token but does not run the client's automatic refresh/retry logic. See the client API plugin, server API plugin, and refresh endpoint for implementation details.
Learn More
See the $api Plugin Reference for detailed implementation information about automatic token handling, refresh logic, and best practices.
Flow Variants
Password Authentication Flow
For password-based authentication (non-OAuth):
Registration stores the email and password hash with the verification code; it does not create an unverified database user. After validating the code, the registration-verification handler calls the required application `onUserPersist` callback to persist the user and merge any returned enrichment. Only after persistence succeeds does it delete the verification code and generate the Aegis authorization code.
SSR Authentication Flow
With nuxtAegis.enableSSR enabled, page requests can resolve authentication from the refresh-token cookie before rendering. The server-only access token and the browser access token have separate lifetimes:
The SSR token uses tokenRefresh.ssrTokenExpiry (default: '5m'). It stays in the server request context and is not hydrated into the browser. SSR authentication does not rotate the refresh token or extend its stored expiration.
The server Nuxt plugin initializes auth-state from event.context.user. Currently that user context contains base identity fields only, even though the SSR JWT includes stored custom claims.
On eligible client startup, the client API plugin calls useAuth().ensureResolved() when no client access token is present. A successful refresh issues a separate client token using token.expiresIn (default: '1h') and applies the configured refresh-token rotation behavior. It does not wait for the SSR token to expire. Startup refresh is skipped on the auth callback page and configured public routes.
Auth routes, API routes, and asset/internal paths are excluded from SSR authentication. Missing, revoked, or expired refresh tokens and authentication failures leave the page unauthenticated; they do not make this hook enforce page access. See SSR Support and Route Protection.
Security Considerations
CSRF Protection
Callbacks require both generated state and the independent initiator proof cookie. Before exchanging tokens or running authentication hooks, the module atomically validates the proof hash, expiry, provider, client ID, and redirect URI, then deletes the matching transaction. Invalid proof/context does not consume the legitimate attempt. Correlated provider denial also consumes it; token-exchange failure cannot reuse it. Callback redirectTo is ignored in favor of stored metadata.
The initial backend is memory-only and single-process. Shared/serverless OAuth deployments are at the moment unsupported, and restarting loses pending transactions. See storage configuration. The proof cookie is not an application session cookie: subsequent bearer JWT authentication does not require it. Cookie-free CLI OAuth initiation remains deferred.
PKCE (Proof Key for Code Exchange)
PKCE binds an authorization request to its token exchange using a challenge and verifier. Nuxt Aegis does not currently implement verifier generation, challenge transport, or verifier submission, and has no supported pkce: true configuration. See the OAuth handler implementation for the current behavior.
Token Security
| Token Type | Lifetime | Storage | Security |
|---|---|---|---|
| OAuth CODE | Provider-dependent | N/A | Provider-defined; no module PKCE support |
| Aegis CODE | 60s | Memory | One-time use, random |
| JWT Token | 1 hour | Client | Signed, short-lived |
| Refresh Token | 7 days | Server + Cookie | HttpOnly, encrypted |
Next Steps
- Token Lifecycle - Token generation, validation, and refresh
- Project Structure - Codebase organization
- Route Protection - Protecting routes and APIs