OAuth 2.0 flow
The flow uses the authorization code grant with PKCE. Your app generates a code verifier and challenge, sends the user to Fanvue to authorize, receives a one-time code on the callback, then exchanges that code (plus the verifier) for access and refresh tokens.PKCE (Proof Key for Code Exchange)
PKCE is required for all OAuth 2.0 flows. It prevents authorization code interception attacks: even if an attacker steals the authorization code, they cannot exchange it for tokens without your originalcode_verifier.
How it works:
- Generate a code verifier: a cryptographically random string (43 to 128 characters).
- Create a code challenge: SHA-256 hash the verifier, then Base64URL-encode it.
- Send the challenge: include
code_challenge(andcode_challenge_method=S256) in the authorization request. - Store the verifier securely until the token exchange (see below).
- Send the verifier: include
code_verifierwhen exchanging the code. - Server verification: Fanvue checks that
SHA256(code_verifier)matches the originalcode_challenge.
Storing the code verifier
Store thecode_verifier between the authorization request and the token exchange.
- Server-side session storage (preferred): Redis, a database, or in-memory session.
- Secure HTTP-only cookies: use the
Secure,HttpOnly, andSameSite=Laxflags. - Encrypted client storage: only if server-side storage is not possible.
Generating verifier and challenge
To support any language: create 32 random bytes for the verifier and Base64URL-encode them (standard Base64, with+ to -, / to _, and trailing = padding removed), giving at least 43 characters. The challenge is the SHA-256 hash of the verifier, Base64URL-encoded the same way, sent with code_challenge_method=S256.
- JavaScript/Node.js
- Python
Authorization URL
Redirect the user to the authorization endpoint with the parameters below.Always include these default scopes:
openid- Required for OpenID Connect; enables ID token generation and access to user identity.offline_access- Provides refresh tokens so your app can obtain new access tokens without re-authentication.offline- Enables long-term access for background operations.
client_id- Your OAuth application’s client ID.redirect_uri- Where users are redirected after authorization (must match your app configuration).response_type=code- Indicates the authorization code flow.scope- Space-separated list of permissions (URL encoded with+).state- Random string to prevent CSRF attacks (verify it matches on callback).code_challenge- [PKCE] The Base64URL-encoded SHA-256 hash of yourcode_verifier.code_challenge_method=S256- [PKCE] Indicates the SHA-256 hashing method.
Token exchange
After receiving the authorization code on your callback, exchange it for access and refresh tokens.How to authenticate the token request
Fanvue registers confidential clients with theclient_secret_basic authentication method. Send your client_id and client_secret in an HTTP Basic Auth header, not in the request body:
client_secret (registered with auth method none), do not send a secret at all. The code_verifier authenticates the exchange on its own. Confirm your app’s type in the Builder area before choosing an auth method.
Required parameters (in the request body):
grant_type=authorization_code- Indicates you are exchanging an authorization code.code- The authorization code received from the callback.redirect_uri- Must match the redirect URI used in the authorization request.code_verifier- [PKCE] The originalcode_verifieryou generated (NOT the challenge).
Handling the callback
Fanvue redirects the user back to yourredirect_uri with the authorization code and state:
- Validate
stateagainst the value you stored, to prevent CSRF. - Retrieve the stored
code_verifierfrom session or cookies. - Exchange the code for tokens (see above), handling failures gracefully.
- Clean up the temporary
stateandcode_verifier. - Store tokens securely, encrypted at rest (a database is preferred over a plain session).
code_verifier and state when you build the authorization URL, then verify and exchange them on the callback.
- Express.js
Token refresh
Access tokens are short-lived (typically 1 hour). Use the refresh token to obtain new access tokens without sending the user back through authorization. Authenticate the refresh request the same way as the token exchange:client_secret_basic via the Basic Auth header, with only grant_type and refresh_token in the body.
401.
- JavaScript/Node.js
401 once after refreshing, store refresh tokens encrypted, always update both tokens from the response, and redirect the user to re-authenticate if a refresh fails.
Managing your OAuth client secret
When you create an app in the Builder area, Fanvue generates a Client ID and a Client Secret. The Client ID is a public identifier; the Client Secret authenticates your backend to Fanvue’s token endpoint. Mishandling it is the single most common cause of serious OAuth incidents.Save the secret immediately
The Client Secret is shown only once, when the app is created. Fanvue does not store it in a retrievable form, so if you close the creation screen without copying it, your only option is to regenerate.- Copy the secret into your secrets manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Doppler, 1Password, etc.) before navigating away from the creation page.
- In the Fanvue App Starter and similar local setups, put the secret into
.env.localasOAUTH_CLIENT_SECRET. Never commit.envfiles to version control.
Store it only on your server
The Client Secret is a server-side credential. Never:- Commit it to a git repository (public or private).
- Ship it in a browser bundle, mobile app binary, or any client-side artefact.
- Log it in application logs, analytics events, or error reports.
- Paste it into shared chat channels or tickets.
Regenerating the secret is a breaking change
You can regenerate the Client Secret at any time from the Builder area. Regeneration:- Immediately invalidates the previous secret. Any running service that still holds the old value starts receiving
invalid_clienterrors from the token endpoint. - Breaks every installation that depends on your backend until each instance is updated with the new secret.
- Does not affect the Client ID or existing access/refresh tokens directly, but your backend cannot refresh expired tokens until it has the new secret.
Error handling
The token and authorization endpoints return standard OAuth errors as JSON witherror and error_description fields. For the full catalogue of HTTP status codes, body shapes, rate-limit headers, and retry guidance shared across the Fanvue API, see API Conventions. In your own code, separate OAuth errors from network errors, retry only on server errors (5xx) and rate limits (429) with exponential backoff (never on 4xx), and surface friendly messages to users rather than raw error codes.
Common OAuth errors and what they mean:
Best practices
Security
- PKCE is mandatory for all OAuth flows.
- Code verifier: 43 to 128 characters, cryptographically secure, stored server-side (session/Redis) or in HTTP-only cookies. Never expose it in URLs, local storage, or client-side JavaScript.
- Always use HTTPS in production.
- Store tokens encrypted at rest, and implement proper token refresh.
- Use the
stateparameter to prevent CSRF (minimum 32 random characters). - Validate that redirect URIs match exactly what is configured in your app.
- Keep your Client Secret server-side and never expose it in client-side code.
User experience
- Clearly explain what permissions your app needs.
- Handle authorization errors gracefully.
- Provide a way for users to disconnect your app.
- Respect rate limits and user privacy.
Token management
- Access tokens are short-lived (typically 1 hour); use refresh tokens to renew them.
- Refresh automatically before expiration, and handle expiry gracefully.
- Revoke tokens when users disconnect.
Support
- Quick Start: see the OAuth App Quick Start to get started.
- Implementation: follow our Quickstart Guide for a complete Next.js example.
- API Reference: check our API documentation.
- App Management: visit the developer portal.
- Template: review the starter template on GitHub.