Skip to content

OAuth2 and BFF patterns

Token endpoint grants, authorization code flow hooks, and backend-for-frontend considerations.

Intended audience: Stakeholders, Business analysts, Solution architects, Developers, Testers

Learning outcomes by role

Stakeholders

  • Understand OAuth and BFF patterns as the supported browser login approach.

Business analysts

  • Describe consent, redirect, and token handoff flows for UX specs.

Solution architects

  • Design PKCE, cookie boundaries, and reverse-proxy rules for auth endpoints.

Developers

  • Implement Nuxt or other BFF clients following the documented sequence.

Testers

  • Automate authorization code, refresh, and negative OAuth cases.

OAuth2 is how browsers and mobile apps sign users in and obtain access and refresh tokens; a BFF (backend-for-frontend) keeps secrets off the device and forwards Bearer tokens to Cadence. Test plans should cover redirect URLs, consent, and token expiry; implementation detail lives in the OAuth2 token endpoint and authorization server grant logic.

  • UX versus security — Users expect SSO-style flows; the BFF pattern avoids putting client secrets in the browser bundle.
  • Compliance — Token lifetimes and consent screens are part of your audit story for interactive access.
  • Flows — Document redirect chains, consent screens, and error pages for each OAuth grant you enable.
  • Environments — Separate OAuth client registrations per environment so staging callbacks never hit production URLs.
  • Cadence exposes /oauth2/* and related routes (see cadence.api.oauth2); the BFF exchanges codes and stores refresh tokens server-side.
  • Redis still backs interactive Bearer sessions after token exchange (jti rows), consistent with JWT sessions.

Interactive clients need standard login and token exchange without embedding long-lived secrets in JavaScript. Cadence implements OAuth2-style endpoints so your SPA or mobile app can use authorization code + PKCE (or password/refresh grants where appropriate) while the API continues to authorize requests with Bearer JWTs backed by Redis sessions.

sequenceDiagram
participant Browser
participant BFF as BFF server
participant Cadence as Cadence API
participant Redis as Redis
Browser->>Cadence: GET /oauth2/authorize
Cadence->>Browser: Redirect with code
Browser->>BFF: Callback with code
BFF->>Cadence: POST /oauth2/token
Cadence->>Redis: session / code storage
Cadence->>BFF: access + refresh tokens

Cadence exposes OAuth2-style endpoints under /oauth2/* and OIDC discovery at /.well-known/openid-configuration. POST /oauth2/token switches on grant_type and calls the matching handle_*_grant helper.

| grant_type | Purpose | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | password | Resource-owner password (validated via AuthService / user repo); requires OAuth2 client credentials when clients are registered. | | refresh_token | Rotates access + refresh via AuthService.refresh (see JWT sessions). | | authorization_code | Exchanges a one-time code from the consent redirect for tokens; uses session_store.consume_oauth2_code and optional PKCE code_verifier. |

Unknown grants return 400 unsupported_grant_type.

Authorization code flow (browser-friendly)

Section titled “Authorization code flow (browser-friendly)”
  1. Client redirects user to GET /oauth2/authorize with client_id, redirect_uri, scope, state, optional PKCE code_challenge.
  2. After login/consent, user is redirected back with a code (stored briefly in Redis via store_oauth2_code).
  3. BFF or confidential client calls POST /oauth2/token with grant_type=authorization_code, code, redirect_uri, client_id/client_secret, and code_verifier if PKCE was used.

Related routes: /oauth2/consent/*, /oauth2/userinfo, POST /oauth2/revoke, POST /oauth2/introspect — see the Security and access API table.

| Topic | Rule | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Secrets | Never ship client_secret in a browser bundle — exchange codes on a server-side BFF or use public clients with PKCE only when your model allows it. | | Transport | Cadence expects Bearer access tokens on API calls; a BFF often stores refresh in http-only cookies and attaches access to upstream requests. | | CORS | Set CADENCE_CORS_ORIGINS for browser origins calling the API directly (see application entry and CORS configuration). |

Social login (Google, GitHub, generic OAuth2)

Section titled “Social login (Google, GitHub, generic OAuth2)”

Cadence supports social login via Google, GitHub, and a generic OAuth2 provider. Configuration is stored in global_settings and bootstrapped at startup from CADENCE_* environment variables.

| Key | Source env var | Purpose | | -------------------------------- | ------------------------------------ | ----------------------------- | | oauth.google.enabled | — | Toggle Google login | | oauth.google.client_id | CADENCE_GOOGLE_CLIENT_ID | Google OAuth client ID | | oauth.google.client_secret | CADENCE_GOOGLE_CLIENT_SECRET | Google OAuth client secret | | oauth.github.enabled | — | Toggle GitHub login | | oauth.github.client_id | CADENCE_GITHUB_CLIENT_ID | GitHub OAuth client ID | | oauth.github.client_secret | CADENCE_GITHUB_CLIENT_SECRET | GitHub OAuth client secret | | oauth.oauth2.enabled | — | Toggle generic OAuth2 login | | oauth.oauth2.client_id | CADENCE_OAUTH2_CLIENT_ID | Generic OAuth2 client ID | | oauth.oauth2.client_secret | CADENCE_OAUTH2_CLIENT_SECRET | Generic OAuth2 client secret | | oauth.oauth2.authorization_url | CADENCE_OAUTH2_AUTHORIZATION_URL | Authorization endpoint URL | | oauth.oauth2.token_url | CADENCE_OAUTH2_TOKEN_URL | Token endpoint URL | | oauth.oauth2.userinfo_url | CADENCE_OAUTH2_USERINFO_URL | Userinfo endpoint URL | | oauth.oauth2.scopes | CADENCE_OAUTH2_SCOPES | Scopes (default: openid email profile) |

All keys are non-overridable (locked at the global level). Enable a provider by setting its *.enabled key to true via PATCH /api/admin/settings/{key}.

  1. Initiate — Client calls GET /oauth2/social/{provider} (provider: google, github, or oauth2). The server builds the provider’s authorization URL with a state parameter for CSRF protection and redirects the browser. The state is stored in Redis via session_store.store_oauth_state(state, provider, code_verifier, ttl_seconds=300) for PKCE and CSRF.
  2. Provider callback — The provider redirects back to GET /oauth2/social/{provider}/callback with an authorization code. The server exchanges the code for provider tokens via the configured authorization_url / token_url / userinfo_url, fetches the user profile, and links or creates a platform user via UserOAuthIdentity (unique on (provider, provider_user_id)).
  3. Issue platform tokens — The OAuthService issues Cadence JWT access and refresh tokens for the authenticated user, backed by a Redis session (jti).

External identities are linked to platform users in the user_oauth_identities table:

| Column | Type | Notes | | ------------------ | ------- | ---------------------------------- | | user_id | UUID7 | → users.id | | provider | string | google, github, or oauth2 | | provider_user_id | string | Provider’s unique user identifier |

The (provider, provider_user_id) pair is unique, ensuring one platform user per provider identity. If a provider identity is not yet linked, the callback creates a new user with the profile data from the provider.

GET /oauth2/social/providers returns a list of enabled social login providers. Use this to render login buttons in the UI dynamically.

  • Grant support is fixed to the three types above; extension grants return 400.
  • Token and consent behavior depends on Redis for sessions and OAuth artifacts; broker/DB issues surface as 401/invalid_grant style errors from domain services.