OAuth and OIDC
The module handles the complete OAuth and OIDC authorization code flow at the gateway level. The browser never sees provider tokens or client secrets. Two handlers manage the flow: one that builds the authorization URL and redirects the user, and one that handles the provider callback, exchanges the code for tokens, verifies the result, and proxies the user data to the IAM service.
Provider configuration
Providers are defined in the OAuthProviders array in the configuration. Each provider is either an OIDC provider (kind: 'oidc') or a generic OAuth provider (kind: 'oauth').
OIDC provider
OIDC providers expose a discovery document at /.well-known/openid-configuration. The module fetches this document automatically using the issuer URL to discover the authorization and token endpoints. Use this kind for Google, Microsoft, Okta, and any provider that supports OpenID Connect.
OAuthProviders: [
{
kind: 'oidc',
name: 'google',
issuer: 'https://accounts.google.com',
clientId: process.env.OAUTH_GOOGLE_CLIENT_ID!,
clientSecret: process.env.OAUTH_GOOGLE_CLIENT_SECRET!,
redirectUri: 'https://app.example.com/oauth/callback/google',
defaultScopes: ['openid', 'email', 'profile'],
supportPKCE: true,
redirectUrlOnSuccess: 'https://app.example.com/dashboard',
redirectUrlOnError: 'https://app.example.com/login',
extraAuthParams: {
access_type: 'offline',
prompt: 'consent'
}
}
]
Generic OAuth provider
Generic OAuth providers require explicit endpoint URLs. Use this kind for GitHub, X (Twitter), LinkedIn, and any provider that does not support OIDC discovery.
{
kind: 'oauth',
name: 'github',
authorizationEndpoint: 'https://github.com/login/oauth/authorize',
tokenEndpoint: 'https://github.com/login/oauth/access_token',
userInfoEndpoint: 'https://api.github.com/user',
clientId: process.env.OAUTH_GITHUB_CLIENT_ID!,
clientSecret: process.env.OAUTH_GITHUB_CLIENT_SECRET!,
redirectUri: 'https://app.example.com/oauth/callback/github',
defaultScopes: ['read:user', 'user:email'],
supportPKCE: true,
redirectUrlOnSuccess: 'https://app.example.com/dashboard',
redirectUrlOnError: 'https://app.example.com/login'
}
Provider fields
/oauth/:name and the callback is /oauth/callback/:name. This value must match exactly.openid to receive an ID token.false.prompt, access_type, or response_mode.client_secret_basic (Authorization header). Use client_secret_post for providers like LinkedIn that require credentials in the request body.iss claim in the ID token.sub, id or a user_id.Omitting these values will cause the flow to fail, use the callbacks options to provide the necessary data when the provider omits it.Authorization redirect
OAuthRedirect handles GET /oauth/:provider. It looks up the provider by name in the configuration and builds the authorization URL.
For OIDC providers, it calls discoverOidc(issuer) to fetch the authorization_endpoint from the discovery document. For generic OAuth providers, it uses the configured authorizationEndpoint directly.
Before redirecting, the handler generates:
- A random
statevalue, signed into a short-lived cookie (state{name}, 3-minute TTL) usingcreateSignedValue - A PKCE verifier and challenge via
makePkcePair(), stored in apkce_v{name}cookie whensupportPKCEistrue - A
noncefor OIDC providers, stored in anonce{name}cookie
All pre-flow cookies are HttpOnly, Secure, and expire in 3 minutes. For providers that use response_mode: form_post, the SameSite attribute is set to None instead of Lax to allow the POST callback to carry the cookies.
useOAuthRoutes(router) registers both the redirect and callback routes:
import { createRouter } from 'h3'
import { useOAuthRoutes } from 'auth-h3client/v1'
const router = createRouter()
useOAuthRoutes(router)
// Registers:
// GET /oauth/:provider
// GET /oauth/callback/:provider
Callback handler
The callback route is handled by two components running in sequence: the OAuthTokensValidations middleware and the OAuthSuccessCallBack handler.
OAuthTokensValidations middleware
Runs first on every callback request. Handles all provider-side validation and token exchange before the handler sees the event.
State and code check
Reads the authorization code and state parameter from the request (query string for GET, form body for POST when response_mode: form_post). If the provider returned an error parameter, clears the OAuth cookies and redirects to redirectUrlOnError. If code is missing, also redirects to redirectUrlOnError.
State cookie verification
Reads the state{name} cookie and verifies the signed value using verifySignedCookie with the keyword auth-oauth.{providerName}. A missing cookie, a signature mismatch, or a stale expiry causes a 400 error. Mismatched iss (OIDC only) also causes a 400 error.
Code exchange
Exchanges the authorization code for tokens by posting to the provider's token endpoint. For OIDC providers, the endpoint is discovered automatically from the .well-known/openid-configuration document. For generic OAuth providers, it uses the configured tokenEndpoint.
Credentials are sent according to tokenAuthMethod:
client_secret_basic: credentials in theAuthorization: Basicheader (default when the provider supports it)client_secret_post: credentials in the POST body
If supportPKCE: true, the PKCE code verifier is included in the request. On exchange failure, redirects to redirectUrlOnError.
ID token verification (OIDC only)
Verifies the ID token in four steps:
- Signature: calls
verifyOAuthTokento check the JWT signature against the provider's JWKS endpoint, confirming the token was issued by the expected provider. - Nonce: checks that the
nonceclaim in the token matches thenonce{name}cookie set during the redirect step. - Authorized party: if the
azpclaim is present, checks it matches the configuredclientId. - Access token binding: if the
at_hashclaim is present, callsatHashCheckto verify it matches a hash of the access token.
If the provider exposes a userinfo_endpoint, also fetches the user profile and verifies that userinfo.sub matches id_token.sub.
User info fetch (OAuth only)
For generic OAuth providers, fetches user info from the configured userInfoEndpoint using the access token as a Bearer token.
On success, sets event.context.provider, event.context.userData, and event.context.accessToken for the handler. Clears the state{name}, pkce_v{name}, and nonce{name} cookies regardless of outcome.
OAuthSuccessCallBack handler
Runs after the middleware. Enriches the user object and proxies it to the IAM service.
Email resolution (OAuth only)
If the user object has no email field and emailCallBack is configured, calls it with the access token to fetch the email from a separate provider endpoint (for example, GitHub's /user/emails API). If no emailCallBack is configured, falls back to scanning the user object's keys and values for a field that matches an email regex. Throws 400 if no email can be resolved.
Extra user info enrichment (OAuth only)
If extraUserInfoCallBacks are configured, calls all of them in parallel with Promise.all and merges each result into the user object using safeObjectMerge with mode: 'drop'. Conflicting reserved fields are silently dropped; a warning is logged for each dropped key.
IAM proxy
Forwards the merged user object to the IAM service at POST /auth/OAuth/{providerName} with the canary_id cookie. On success (200 or 201), the IAM service creates or updates the user account and returns session credentials.
Cookie setting and redirect
Sets the __Secure-a (access token) and a-iat (issued-at) cookies on the response. Forwards the Set-Cookie headers from the IAM response (which carry the session refresh token cookie). Returns HTTP 200 with a temporary HTML page that redirects the browser to redirectUrlOnSuccess.
Token cookie setup
After a successful OAuth callback, the same cookies are issued as in the standard login flow:
| Cookie | Content | Attributes |
|---|---|---|
__Secure-a | Access token | HttpOnly, Secure, SameSite=Strict |
a-iat | Access token issued-at | HttpOnly, Secure, SameSite=Strict |
session | Refresh token (from IAM) | Forwarded from IAM response |
Provider registration in the IAM service
The module handles the OAuth flow at the BFF layer and proxies the resulting user data to the IAM service. The IAM service must have the same provider registered in its configuration to accept the data. See IAM OAuth for the server-side provider setup.