- an app in the Developer Area, with its Client ID and Client Secret
- the exact redirect URI your callback uses, added on the app’s Authentication tab, and the scopes you need ticked there
- a server, because the token exchange carries your client secret, and somewhere server-side to keep a session
The flow at a glance
Your app generates a code verifier and challenge, sends the user to Fanvue to authorise, receives a one-time code on the callback, then exchanges that code plus the verifier for access and refresh tokens.Generate the PKCE verifier and challenge
PKCE (Proof Key for Code Exchange) is required on every flow. An attacker who steals the authorisation code cannot exchange it for tokens without your originalcode_verifier.
- Generate a code verifier: a cryptographically random string of 43 to 128 characters.
- Create a code challenge: SHA-256 hash the verifier, then Base64URL-encode the digest.
- Send the challenge: include
code_challengeandcode_challenge_method=S256in the authorisation request. - Store the verifier until the token exchange. See Storing the code verifier.
- Send the verifier: include
code_verifierwhen exchanging the code. - Server verification: Fanvue checks that
SHA256(code_verifier)matches thecode_challengeit received.
Storing the code verifier
Keep thecode_verifier between the authorisation request and the token exchange in one of these places, in order of preference:
- Server-side session storage (preferred): a session store backed by Redis or a database.
- Secure HTTP-only cookies: set the
Secure,HttpOnlyandSameSite=Laxflags. - Encrypted client storage: only if server-side storage is not possible.
Generating verifier and challenge
Create 32 random bytes for the verifier and Base64URL-encode them: standard Base64 with+ replaced by -, / replaced by _ and trailing = padding removed. The result is 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
Build the authorisation URL
Redirect the user tohttps://auth.fanvue.com/oauth2/auth with the parameters below. Always include the three system scopes alongside your own:
openidenables OpenID Connect and theid_token.offline_accessreturns a refresh token so your app can obtain new access tokens without the user present.offlineenables long-term access for background work.
Token exchange
When the authorisation code arrives on your callback, exchange it for access and refresh tokens.How to authenticate the token request
Fanvue registers every app as a confidential client with theclient_secret_basic authentication method, so your client_id and client_secret go in an HTTP Basic Auth header, not in the request body:
expires_in is the access token lifetime in seconds for this response; read it rather than assuming a fixed lifetime. scope lists what was granted, which can be fewer scopes than you requested when the account type cannot use some of them.
Handling the callback
Fanvue redirects the user back to yourredirect_uri with the authorisation code and state:
- Validate
stateagainst the value you stored. A mismatch means the request did not originate from your app; reject it. - Retrieve the stored
code_verifierfrom the session or cookie. - Exchange the code for tokens as shown in Token exchange.
- Delete the temporary
stateandcode_verifier. - Store the tokens encrypted at rest. A database is preferred over a plain session.
code_verifier and state when you build the authorisation URL, then verify and exchange them on the callback.
- Express.js
Token refresh
Access tokens are short-lived. Use the refresh token to obtain a new access token without sending the user back through authorisation. 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.
With the Builder SDK
The Builder SDK implements this whole flow, so you can skip the hand-written client below:createAuthorizationUrlbuilds the authorise URL and returns thecodeVerifierandstateto store.exchangeCodeForTokenandrefreshAccessTokencall the token endpoint and return aResultinstead of throwing.- For a cookie-based off-platform session,
getAuthenticatedClientreads the session, refreshes the token when it is close to expiry and returns an API client ornull. See Off-platform apps. - For apps with several workers,
createTokenStoreContext,storeTokenSetandgetAccessTokengive you an encrypted, compare-and-swap token store that refreshes once per subject. See Storage and security.
By hand
This client refreshes before expiry with a buffer, serialises concurrent refreshes through one promise, and retries once on a401.
- JavaScript/Node.js
- Refresh before expiry, with a buffer of 5 to 10 minutes.
- Serialise refreshes for one chain with a lock or shared promise.
- Retry a
401once after refreshing. A second401is terminal: send the user through authorisation again. - Store refresh tokens encrypted and update both tokens from every response.
- Send the user back through authorisation when a refresh fails with
invalid_grant.
Managing your OAuth client secret
When you click Create app in the Developer Area, the dialog that opens shows a Client ID and a Client Secret. The Client ID is a public identifier. The Client Secret authenticates your backend to the token endpoint.Save the secret immediately
The dialog shows the Client Secret once. Fanvue does not store it in a retrievable form, so if you close the dialog without copying it, your only option is Reset secret.- Copy the secret into your secrets manager before you close the dialog.
- In a local setup, put the secret in
.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 or mobile app binary.
- Write it to application logs, analytics events or error reports.
- Paste it into shared chat channels or tickets.
Resetting the secret is a breaking change
You can reset the Client Secret at any time with Reset secret on the Authentication tab. The confirmation warns that the current secret is deleted immediately and that the reset can’t be undone. A reset:- Stops the old secret working at once. Any running service that still holds the old value receives
invalid_clientfrom the token endpoint. - Breaks every installation that depends on your backend until each instance holds the new secret.
- Leaves the Client ID and existing access and refresh tokens unchanged, but your backend cannot refresh tokens until it has the new secret.
Error handling
The token and authorisation endpoints return standard OAuth errors as JSON witherror and error_description fields. Handle them separately from network errors. Retry only on server errors (5xx) and rate limits (429), with exponential backoff, and never on other 4xx responses. Show users a plain message rather than the raw error code. Error responses covers the API’s own errors.
Best practices
Security
- PKCE is mandatory on every flow.
- Code verifier: 43 to 128 characters, cryptographically random, stored server-side or in an HTTP-only cookie. Never in URLs, local storage or client-side JavaScript.
- Use HTTPS in production.
- Store tokens encrypted at rest.
- Use a
stateparameter of at least 32 random characters and verify it on the callback. - Register the exact redirect URIs you use on the Authentication tab or in
oauth.redirectUris. - Keep the Client Secret on your server.
User experience
- Explain which scopes your app needs and why before the consent screen.
- Show a plain message on authorisation errors and offer a retry.
- Give users a way to disconnect your app.
Token management
- Read
expires_inand refresh before expiry. - Handle a
401by refreshing once, then retrying. Treat a second401as terminal and send the user through authorisation again. - Discard stored tokens when a user disconnects.