The Quick Start covers the happy path in 5 steps. This guide is the deep version: three implementation patterns (SPA, traditional backend, native mobile),Documentation Index
Fetch the complete documentation index at: https://docs.cativa.digital/llms.txt
Use this file to discover all available pages before exploring further.
id_token validation via JWKS, expected error behaviors and what to do with the token when it expires.
Scenario
You have an app — could be a React SPA on a different domain, a Rails portal with server-side sessions, or a native iOS/Android app. Your community already lives on Cativa and you want users to log into your app with the same account they use on the community, without creating separate credentials. Cativa exposes a standard OIDC IdP per tenant. Any OIDC client library (oidc-client-ts, auth0/spa-js, next-auth, passport-openidconnect, AppAuth-iOS, AppAuth-Android) can talk to it from the tenant’s discovery URL.
Prerequisites
- An OAuth App registered in the Console — go to app.cativa.digital/admin/developers, OAuth Apps tab, click Create app. Save the
client_idandclient_secret(the secret is shown only once). - A redirect URI registered in the same app. You can add multiple (e.g. production + staging + localhost).
- The
customerName(tenant slug) — confirm with the community admin. It’s the tenant’s public Cativa subdomain.
Cativa SSO endpoints follow OIDC and are organized per tenant:
https://apis.cativalab.digital/social/v1/sso/{customerName}/.... The discovery document lives at https://apis.cativalab.digital/social/v1/sso/{customerName}/.well-known/openid-configuration and lists every endpoint and supported algorithm (S256 for PKCE, ES256 for id_token).Pick the right flow
- SPA with PKCE
- Node/Express backend
- Mobile with deep link
When to use: your app is a static frontend (React/Vue/Svelte) served by CDN, with no trusted backend to hold the 1. Generate
The 2. Redirect to
Don’t persist the
client_secret. PKCE (Proof Key for Code Exchange) replaces the client secret with a verifier/challenge pair generated per login.1. Generate code_verifier and code_challenge
The code_verifier is a random string that stays in the browser. The code_challenge is the SHA-256 of the verifier, base64url-encoded — that one goes into /authorize.2. Redirect to /authorize
3. Handle the callback
Cativa redirects the user tohttps://myapp.com/callback?code=...&state=.... Validate state and exchange the code for tokens.Since SPAs cannot hold a client_secret, /token accepts PKCE as proof: you send the original code_verifier (not the challenge) and Cativa recomputes SHA-256 and compares with the code_challenge it stored from step 2.4. Fetch the profile and start the local session
access_token in localStorage — it’s vulnerable to XSS. Keep it in memory (SPA state) or sessionStorage if you accept losing the session across tabs.Validate the id_token via JWKS
The id_token returned by /token is a JWT signed with ES256. You must validate the signature before trusting any claim (sub, email, etc.) — that prevents an attacker from substituting a forged token.
The public key for validation is published at the tenant’s JWKS:
Node.js with jose
Python with PyJWT
Session and token refresh
Theaccess_token returned by Cativa SSO is short-lived (configured per OAuth App, default 1h). Two strategies:
- Silent re-login when it expires — when
expires_inreaches 0, redirect the user back through/authorize. If they still have an active Cativa session, the IdP returns a fresh code without prompting for a password again (single sign-on). - Treat the token as a session bound to your app — store only the identity (
sub,email) on your session and issue your own JWT/cookie. The Cativaaccess_tokenis used only at login time.
Today the Cativa OIDC
/sso/{customerName}/token endpoint does not support grant_type=refresh_token — only authorization_code. Refresh-token flow for external OAuth apps is on our roadmap. For now, use one of the two strategies above. When support ships, this page will be updated with an example.Logout
There is currently no dedicated OIDCend_session endpoint for external OAuth apps. The recommended flow:
- On your side: drop the local session (delete cookie/session/Keychain), redirect the user to a “logged out” destination.
- Optional, if you also want to log them out of the Cativa community: redirect them to
https://{customerName}.cativa.digital/logout(replacing{customerName}with the tenant subdomain).
A standard OIDC
end_session endpoint for external partners is on our roadmap. When available, this page will be updated with cURL and a post_logout_redirect_uri example.Common errors
`error: invalid_request, error_description: redirect_uri not authorized`
`error: invalid_request, error_description: redirect_uri not authorized`
`error: invalid_grant, error_description: Authorization code expired`
`error: invalid_grant, error_description: Authorization code expired`
`error: invalid_grant, error_description: Authorization code already used`
`error: invalid_grant, error_description: Authorization code already used`
`error: invalid_client, error_description: invalid client_secret`
`error: invalid_client, error_description: invalid client_secret`
Check:
- You didn’t swap
client_idandclient_secret. - The secret hasn’t been rotated in the Console (generating a new one invalidates the old).
- There’s no extra whitespace/newline in the env var (this happens with
cat .envwhen the file came from Windows).
`error: invalid_grant, error_description: PKCE verification failed`
`error: invalid_grant, error_description: PKCE verification failed`
The
code_verifier sent to /token doesn’t match the code_challenge sent to /authorize. Common causes: you generated a new verifier before the callback (lost the original), you’re SHA-256-ing different bytes (UTF-8 vs ASCII), or you’re applying standard base64 instead of base64url.`401 Unauthorized` on /userinfo
`401 Unauthorized` on /userinfo
The user can sign in but the `id_token` fails my JWKS validation
The user can sign in but the `id_token` fails my JWKS validation
Confirm:
algorithms: ['ES256'](Cativa signs with ES256, not RS256).issuerexactlyhttps://apis.cativalab.digital/social/v1/sso/{customerName}(no trailing slash).audienceis yourclient_id.- Your library is fetching JWKS from the right issuer (and caching it, so you don’t hit the endpoint on every login).
Next steps
Badges as Permissions
How the
sub from id_token shows up in the community and how badges control what that user can access.Quick Start: API Key
For server-to-server calls (no interactive user), use an API Key instead of OAuth.
