Skip to content

Document the origin and redirect URI prerequisites for serving the admin console #96

Description

@Bccorb

Summary

Serving the admin console changes the origin that browser ceremonies carry, and for OAuth it changes the full redirect URI as well. Two separate allowlists validate these values, and neither requirement is documented. Both fail the same confusing way: the flow starts normally and only fails partway through, so the configuration looks correct right up to the point it does not work.

Both requirements apply to both console deployment shapes:

  1. Served directly by this API (SERVE_ADMIN_DASHBOARD=true) at the API's own origin, for example https://auth.example.com/console.
  2. Reverse-proxied by an adopter's API through createSeamlessConsoleProxy from @seamless-auth/express, so the console loads from the adopter's origin, for example https://api.example.com/console.

The second shape is the one adopters hit as soon as they follow the console wiring guidance, because the console deliberately runs same-origin with their /auth mount.

Requirement 1: the console's origin must be in ORIGINS

Passkey ceremonies started in the console carry the origin the console was loaded from, and WebAuthn verification checks it against origins in system_config (seeded from ORIGINS).

  • src/controllers/stepUp.ts:197 sets expectedOrigin: origins
  • src/controllers/webauthn.ts:224 and src/controllers/webauthn.ts:489 do the same

Reproduced with the console proxied to http://localhost:3000/console while ORIGINS=http://localhost:5173,http://localhost:5174:

  • POST /step-up/webauthn/start returned 200 with a challenge, rpId: localhost, and the user's registered credential
  • the browser prompted normally
  • the finish step failed, so step-up never completed and session revocation stayed blocked

Adding http://localhost:3000 to ORIGINS resolved it with no code change. RPID does not need to change, since the RP ID ignores the port.

Requirement 2: the console's callback must be in each provider's redirectUris

The dashboard builds its OAuth redirect with useHref("/oauth/callback"), which applies the router basename, so the /console build sends <origin>/console/oauth/callback rather than /oauth/callback. That is a different path as well as a different host from a typical web app.

allowedRedirect in src/services/oauthService.ts compares by exact string once a provider has any redirectUris:

if (allowedValues.length > 0) {
  return allowedValues.some((allowedValue) => value === allowedValue);
}

So the console's callback URL has to be listed explicitly in each provider's redirectUris. A near miss on host or path is rejected.

Reproduced against the same provider and session, varying only the redirect URI:

  • http://localhost:3000/console/oauth/callback returned 400 with {"error":"OAuth start failed"}
  • http://localhost:5173/oauth/callback returned 200 with a full authorization URL

Adding http://localhost:3000/console/oauth/callback to the provider's redirectUris resolved it.

Worth stating explicitly in the docs: the identity provider must also have the same URL registered as an authorized redirect URI. This API accepting it is not sufficient, and the provider rejects with redirect_uri_mismatch at the consent screen otherwise.

Suggested documentation changes

  • docs/configuration.md, WebAuthn section (the ORIGINS row): state that the origin serving the admin console must be included, alongside the app origins.
  • docs/configuration.md, Admin dashboard section (near SERVE_ADMIN_DASHBOARD and ADMIN_DASHBOARD_DIR): list both prerequisites for both shapes above, since that is where someone enabling the console is reading.
  • docs/oauth.md: note the console callback shape (<origin>/console/oauth/callback), that it must appear in each provider's redirectUris, and that it must also be registered with the identity provider.
  • Include the symptoms so they are searchable: passkey sign-in and step-up start normally and fail at finish, and OAuth start returns 400 before any redirect happens.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions