OAuth and OIDC

Provider configuration, the authorization redirect, PKCE, the callback exchange, ID token verification via JWKS, at_hash validation, and how user data is merged safely.

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.

server/plugins/auth.ts
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

kind
"oidc" | "oauth" required
Selects the provider variant. OIDC providers use discovery; generic OAuth providers require explicit endpoint URLs.
name
string required
Identifier used in the route path. The authorization URL is /oauth/:name and the callback is /oauth/callback/:name. This value must match exactly.
clientId
string required
OAuth application client ID from the provider dashboard.
clientSecret
string required
OAuth application client secret. Never expose this to the browser.
redirectUri
string required
The full callback URL registered with the provider. Must match exactly, including the protocol and path.
defaultScopes
string[]
Scopes to request. For OIDC providers, include openid to receive an ID token.
supportPKCE
boolean
Enables PKCE (Proof Key for Code Exchange). Recommended for all providers that support it. Generates a code verifier and challenge pair for each flow and stores the verifier in a short-lived signed cookie. Default to false.
redirectUrlOnSuccess
string required
Where to send the user after a successful authentication and cookie issuance.
redirectUrlOnError
string required
Where to send the user when the flow fails at any point.
extraAuthParams
Record<string, string>
Additional query parameters appended to the authorization URL. Useful for provider-specific options such as prompt, access_type, or response_mode.
tokenAuthMethod
"client_secret_post" | "client_secret_basic"
Token endpoint authentication method. Defaults to client_secret_basic (Authorization header). Use client_secret_post for providers like LinkedIn that require credentials in the request body.
emailCallBack
Function
Generic OAuth only. A function that fetches the user's email from a separate endpoint when the main user-info endpoint does not return one. Useful for GitHub, where email requires a separate API call.
extraUserInfoCallBacks
Function[]
Generic OAuth only. Additional functions called after the main user-info fetch to enrich the user object with data from other endpoints.
issuer
string
OIDC only. The provider's issuer URL. Used to build the discovery document URL and to validate the iss claim in the ID token.
The IAM service require clients to provide a user email and at least one of the following withing a OAuth flow: 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 state value, signed into a short-lived cookie (state{name}, 3-minute TTL) using createSignedValue
  • A PKCE verifier and challenge via makePkcePair(), stored in a pkce_v{name} cookie when supportPKCE is true
  • A nonce for OIDC providers, stored in a nonce{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.

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 the Authorization: Basic header (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:

  1. Signature: calls verifyOAuthToken to check the JWT signature against the provider's JWKS endpoint, confirming the token was issued by the expected provider.
  2. Nonce: checks that the nonce claim in the token matches the nonce{name} cookie set during the redirect step.
  3. Authorized party: if the azp claim is present, checks it matches the configured clientId.
  4. Access token binding: if the at_hash claim is present, calls atHashCheck to 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.

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.


After a successful OAuth callback, the same cookies are issued as in the standard login flow:

CookieContentAttributes
__Secure-aAccess tokenHttpOnly, Secure, SameSite=Strict
a-iatAccess token issued-atHttpOnly, Secure, SameSite=Strict
sessionRefresh 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.

Logo