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.
Summary for stakeholders
Section titled “Summary for stakeholders”- 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.
Business analysis
Section titled “Business analysis”- 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.
Architecture and integration
Section titled “Architecture and integration”- Cadence exposes
/oauth2/*and related routes (seecadence.api.oauth2); the BFF exchanges codes and stores refresh tokens server-side. - Redis still backs interactive Bearer sessions after token exchange (
jtirows), consistent with JWT sessions.
Why this exists
Section titled “Why this exists”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.
How it fits the platform
Section titled “How it fits the platform”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 tokensCadence 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.
Supported grants (POST /oauth2/token)
Section titled “Supported grants (POST /oauth2/token)”| 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)”- Client redirects user to
GET /oauth2/authorizewithclient_id,redirect_uri,scope,state, optional PKCEcode_challenge. - After login/consent, user is redirected back with a
code(stored briefly in Redis viastore_oauth2_code). - BFF or confidential client calls
POST /oauth2/tokenwithgrant_type=authorization_code,code,redirect_uri,client_id/client_secret, andcode_verifierif PKCE was used.
Related routes: /oauth2/consent/*, /oauth2/userinfo, POST /oauth2/revoke, POST /oauth2/introspect — see the Security and access API table.
Key properties
Section titled “Key properties”| 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.
Provider configuration keys
Section titled “Provider configuration keys”| 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}.
Social login flow
Section titled “Social login flow”- Initiate — Client calls
GET /oauth2/social/{provider}(provider:google,github, oroauth2). The server builds the provider’s authorization URL with astateparameter for CSRF protection and redirects the browser. Thestateis stored in Redis viasession_store.store_oauth_state(state, provider, code_verifier, ttl_seconds=300)for PKCE and CSRF. - Provider callback — The provider redirects back to
GET /oauth2/social/{provider}/callbackwith an authorization code. The server exchanges the code for provider tokens via the configuredauthorization_url/token_url/userinfo_url, fetches the user profile, and links or creates a platform user viaUserOAuthIdentity(unique on(provider, provider_user_id)). - Issue platform tokens — The
OAuthServiceissues Cadence JWT access and refresh tokens for the authenticated user, backed by a Redis session (jti).
UserOAuthIdentity
Section titled “UserOAuthIdentity”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.
Available providers endpoint
Section titled “Available providers endpoint”GET /oauth2/social/providers returns a list of enabled social login providers. Use this to render login buttons in the UI dynamically.
What this is not
Section titled “What this is not”Limitations
Section titled “Limitations”- 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.