BluAuth
Docs
Sign in
User FAQ
  • Reset my password
  • I can't sign in
  • Didn't get reset email
  • Account linking
  • Session expiry
  • Two-factor auth
Admin Guides
Theme Studio
  • Overview
  • Layouts
  • Styling tokens
  • Concept copy
  • Assets & backgrounds
  • Advanced CSS
Admin Shell
  • Users
  • Providers
  • Clients
  • Invitations
Integrations
  • OIDC flow
  • Legacy OAuth flow
  • Provider token brokering
  • Email triggers
  • Webhook events
  • Session contract
Reference
  • API
  • Error codes
  • Event shapes
  • Design tokens
Runbooks
  • Deployment
  • Local operations

Legacy OAuth Flow

Before BluAuth shipped its OIDC provider, Blu apps integrated through a custom broker route pair: /oauth2/login → upstream provider → /oauth2/callback → redirect back to the client with ud-* query parameters carrying identity claims.

These routes are preserved for compatibility with apps that predate BluAuth's OIDC implementation. Do not use them for new integrations. Use the modern OIDC flow instead.

Source: server/routes/oauth2/login.get.ts, server/routes/oauth2/callback.ts.

When to use this path

Use the legacy flow if — and only if — one of these applies:

  • Your app was already integrated with BluAuth's /oauth2/login broker before the OIDC endpoints existed, and you have not finished migrating.
  • You are explicitly planning a short-lived migration cutover and need both flows to coexist temporarily.

Use OIDC for everything else. The /oauth2/* routes:

  • Emit unsigned identity parameters. An intermediary who controls the redirect_uri can forge them.
  • Provide no access token, no ID token, no JWKS, no introspection, no revocation.
  • Have no standard library support — every integration is bespoke.
  • Do not participate in session federation; each downstream app maintains its own session independently.

Flow overview

[Your App]                [BluAuth]                 [Upstream IdP]
    |                         |                           |
    | 1. redirect /oauth2/login ------------------------> |
    |                         | 2. provider authorize --> |
    |                         |                           |
    |                         | 3. user authenticates     |
    |                         |                           |
    |                         | 4. <-- provider callback  |
    | 5. <-- redirect to redirect_uri?ud-* -------------- |

1. Redirect to /oauth2/login

GET https://auth.example.com/oauth2/login
  ?provider=google
  &returnTo=https%3A%2F%2Fapp.example.com%2Fcallback
  &client_id=7f9b0e7e-...-a11c

Query parameters:

ParameterRequiredNotes
providerYesThe upstream provider slug (google, github, microsoft, or a configured custom OIDC slug). Must correspond to an active row in identity_providers.
returnToYesAbsolute URL where BluAuth will redirect after sign-in. Must match one of the client's registered redirect URIs, or a same-origin relative path.
client_idRecommendedClient UUID. Required when requesting additional_scopes (provider-token brokering). When present, returnTo is validated against that client's redirect URI list.
additional_scopesOptionalSpace-separated upstream scopes. Only honored if the client's allowedProviderTokens includes the provider.
promptOptionalOIDC prompt value. Automatically downgraded if the upstream IdP doesn't advertise it.
intentOptionallink to link an additional provider to an already-signed-in user.

2. BluAuth redirects to the upstream provider

BluAuth resolves the provider config (Valkey-cached from identity_providers), loads the upstream's discovery document, and redirects the user to the upstream authorization endpoint with BluAuth's own redirect_uri pointing at /oauth2/callback.

3. User authenticates upstream

The user completes sign-in with Google, GitHub, Microsoft, or the configured custom OIDC provider. If the provider rejects the request (e.g. access_denied), BluAuth propagates a sanitized error=consent_denied or error=provider_error back to your returnTo.

4. BluAuth handles the callback

At /oauth2/callback, BluAuth:

  1. Validates the state parameter against a short-lived record in upstream_oauth_state.
  2. Exchanges the code with the upstream provider's token endpoint.
  3. Validates the upstream id_token signature (when the provider is configured for strict verification).
  4. Resolves or provisions the local BluAuth user via resolveIdentityFromProviderLogin — matching on verified email aliases, handling link-mode consent, or creating a new canonical user.
  5. Issues a local BluAuth session (sets the Better Auth session cookie).
  6. Redirects to returnTo with ud-* parameters carrying identity claims.

5. Redirect back to your app

https://app.example.com/callback
  ?state=<echoed-if-you-sent-one>
  &ud-sub=<user-uuid>
  &ud-email=user%40example.com
  &ud-email_verified=true
  &ud-name=User%20Name
  &ud-picture=https%3A%2F%2F...

The ud-* parameters are URL-encoded strings carrying the same identity facts that the OIDC ID token carries. They are not signed. Apps historically accepted them at face value because the broker ran on a trusted boundary, but any new app reading these should migrate.

Error redirects

When something fails before BluAuth can identify the user, the callback handler redirects to returnTo (or /login as a last resort) with a sanitized error code:

error= valueMeaning
consent_deniedUpstream provider returned access_denied.
provider_errorUpstream provider returned a non-access_denied error.
missing_paramsUpstream callback arrived with no code or state.
account_claimed_by_other_userThe upstream identity is already linked to a different BluAuth user.
provider_already_linkedProvider is already linked — link-mode requested but redundant.
ambiguous_email_aliasMultiple verified aliases match; manual resolution required.
user_not_foundTarget user (link-mode) no longer exists.
user_create_failedCanonical user creation failed — check logs with the request ID.
session_failed / internalUncategorized internal error.

These values are stable and safe to branch on in downstream UX.

Security implications

  • The ud-* parameters are visible in the browser address bar, referer headers, and access logs. Do not put anything beyond the identity claims above into them.
  • They are not signed. A compromised TLS terminator or a man-in-the-browser extension can tamper with them. OIDC's id_token (JWS) defends against this; ud-* does not.
  • The state parameter is round-tripped but the identity claims are not bound to state — an attacker who can forge a redirect can substitute values.
  • Because the broker sets a local BluAuth session cookie as part of the callback, downstream apps that share the auth.example.com origin can additionally call /api/auth/security-state to reconfirm the authenticated user server-side.

Treat ud-* as presentation-only hints and reconfirm identity server-side with a session check or (preferably) an OIDC round-trip.

Migrating off

If you own an existing Blu app on the legacy flow:

  1. Ask a BluAuth admin to register an OIDC client for your app (/admin/clients). Reuse the redirect URI you already have registered for the legacy path.
  2. Pick an OIDC library for your stack (recommendations).
  3. Run both flows side-by-side behind a feature flag. Compare the ud-sub value with the OIDC ID token's sub — they will differ (OIDC uses pairwise subjects per client), but userinfo.emails will match the ud-email alias list.
  4. Migrate session handling to rely on the ID token / userinfo response rather than ud-* params. Keep server-side session validation idempotent during cutover.
  5. Cut traffic over to OIDC. Remove the legacy /oauth2/login redirect after one full release cycle of zero-traffic on the old handler.

OIDC vs. legacy quick reference

CapabilityOIDC flowLegacy /oauth2/* flow
Signed identityID token (JWS, ES256)None — plain ud-* query params
Access tokenES256 JWT, 1-hour TTLNone
Userinfo endpointYesNo
Token revocation (RFC 7009)YesNo
Token introspection (RFC 7662)YesNo
Logout endpointRP-initiated (RFC 8414)Implicit; clear session at BluAuth cookie
Pairwise subjectsYesNo — raw user UUID in ud-sub
PKCERequiredN/A
Library supportAny conformant OIDC clientBespoke integration per app
Provider-token brokeringSupportedNot supported
Webhook-eligibleSame eventsSame events

The two flows share identity resolution, provider config, session creation, and webhook emission. The only real difference is how identity is handed back to the calling app — a signed JWT vs. unsigned query params.

Session side-effects

Completing either flow leaves the user with an active BluAuth session (Better Auth cookie on the auth.example.com origin) and a row in the sessions table. The legacy flow does not grant the downstream app any cryptographic proof of that session — the proof lives only in the unsigned ud-* params. Downstream apps wanting stronger guarantees should either:

  1. Reconfirm identity server-side by calling /api/auth/security-state on the BluAuth origin (only works for same-origin apps), or
  2. Switch to OIDC and validate the ID token.

Provider-token brokering is not supported

The legacy path cannot carry upstream provider tokens. If your app needs brokered tokens for Google Drive, Microsoft Graph, or any other upstream API, you must integrate via OIDC — Provider Token Brokering is keyed on BluAuth-issued access tokens, which the legacy flow does not produce.

Webhook emission

The legacy flow still publishes the same lifecycle events as OIDC:

  • session.created fires on successful sign-in through /oauth2/callback.
  • user.created fires on first sign-in when a new canonical user is provisioned.
  • account.linked fires when intent=link completes.

If your downstream app subscribes to these webhooks, it receives events from both flows uniformly — there is no separate legacy event stream.

Lifecycle

The legacy routes read from the same identity_providers and oauth_clients tables as the OIDC flow — changes to a provider's discovery document or a client's redirect URI list affect both paths simultaneously. The routes are not deprecated on any fixed timeline. BluAuth will keep them operational as long as at least one Blu app depends on them.

That said: there is no new feature investment in this path. Improvements to consent UX, token telemetry, session revocation, and provider-token brokering all target OIDC first. If you file an issue against the legacy flow, the likely answer is "migrate to OIDC."

Checklist before shipping a legacy-flow integration

Only if you absolutely cannot use OIDC today:

  • Confirm with a BluAuth admin that no OIDC client can be registered for your app.
  • Register your returnTo with the client's redirectUris.
  • Verify every downstream consumer of ud-* treats the values as hints, not facts.
  • Ensure your app does a server-side identity reconfirmation before any sensitive action.
  • Open a migration ticket referencing this doc — the legacy flow is not a destination.

On this page

  • When to use this path
  • Flow overview
  • 1. Redirect to /oauth2/login
  • 2. BluAuth redirects to the upstream provider
  • 3. User authenticates upstream
  • 4. BluAuth handles the callback
  • 5. Redirect back to your app
  • Error redirects
  • Security implications
  • Migrating off
  • OIDC vs. legacy quick reference
  • Session side-effects
  • Provider-token brokering is not supported
  • Webhook emission
  • Lifecycle
  • Checklist before shipping a legacy-flow integration
DocsPrivacyTerms
© 2026 Blu Digital Group