Provider Token Brokering
Some downstream apps need more than the user's identity — they need to call the upstream provider's APIs on the user's behalf. Example: media-conductor reading a user's Google Drive, or a workflow tool posting to their Microsoft Teams.
Claxx Auth brokers those upstream tokens so downstream apps never implement upstream OAuth themselves. The user consents once, at Claxx Auth. Claxx Auth holds the refresh token, refreshes upstream tokens on demand, and hands downstream apps a short-lived access token for each request.
This page covers how to ask for a brokered token, how Claxx Auth decides whether to grant it, and the error semantics a client must handle.
Why brokering, not redirection
The architectural mandate is that no downstream Blu app implements standalone OAuth against a third-party IdP. Every third-party identity relationship is owned by Claxx Auth. The benefits:
- Single consent surface. The user consents to "media-conductor via Claxx Auth" once, not per-app.
- Centralized refresh. Refresh tokens live in one encrypted store and rotate correctly even for Microsoft's rotating-refresh-token model.
- Revocation in one place. Admin-level revocation from
/admin/users/:idinvalidates everything downstream. - No secret distribution. Downstream apps never see upstream client secrets or refresh tokens.
The principle
- Claxx Auth owns the upstream OAuth relationship. The user consents during Claxx Auth sign-in, and Claxx Auth stores the upstream refresh token (KMS-encrypted, at rest in the
accountstable). - Downstream apps call Claxx Auth, not the upstream provider, when they need a fresh upstream access token.
- Refresh is transparent. Claxx Auth refreshes upstream tokens on demand; the downstream app receives only the short-lived access token.
- Tokens are per-client. Each OAuth client must be explicitly allowed to broker tokens for a given provider (via
oauth_clients.allowed_provider_tokens).
Consent and scope allowlisting
Two allowlists apply per request:
- Client-level: Admins configure each OAuth client's
allowedProviderTokens— an array of provider slugs (e.g.["google", "microsoft"]). A request forgoogletokens from a client that doesn't listgooglereturnsunauthorized_client. - User-level: Each
(user, client, provider)tuple has a grant record inprovider_token_grantscapturing the exact scopes the user consented to. Downstream apps can only request scopes that are a subset of the granted scopes.
When a user signs in via Claxx Auth with additional_scopes=<upstream-scopes> passed through the authorize flow, the upstream provider's consent screen lists those scopes. On success, the grant record is written or updated. Subsequent token requests for that client check the grant.
Endpoint
POST https://auth.example.com/api/provider-tokens/<provider>
Authorization: Bearer <bluauth-access-token>
Content-Type: application/json
{
"requiredScopes": ["https://www.googleapis.com/auth/drive.readonly"]
}
Where <provider> is the provider slug — one of google, microsoft, github, or a custom OIDC provider slug configured in /admin/providers.
Authentication is the Claxx Auth access token (JWT, ES256) issued to your client via the OIDC flow. The token must belong to the same user whose upstream tokens you want.
Source: server/api/provider-tokens/[provider].post.ts.
Request parameters
| Field | Required | Notes |
|---|---|---|
requiredScopes | No | Array of upstream scope strings. When omitted, Claxx Auth returns whatever scopes were last granted for this user/client/provider. When provided, Claxx Auth verifies every entry is present in the stored grant before returning the token. |
Scopes use the upstream provider's syntax:
- Google — full URLs like
https://www.googleapis.com/auth/drive.readonly,https://www.googleapis.com/auth/calendar.events. - Microsoft — delegated permission names like
Mail.Read,Files.Read.All. - GitHub — OAuth scopes like
repo,read:org.
Claxx Auth does not translate or normalize scope strings; pass them exactly as the upstream documents them.
Response
{
"success": true,
"data": {
"accessToken": "ya29.a0AQ...",
"expiresIn": 3240,
"provider": "google",
"scopes": ["https://www.googleapis.com/auth/drive.readonly", "openid", "email"],
"clientMetadata": {
"clientId": "533765293212-abc123.apps.googleusercontent.com",
"appId": "533765293212"
}
}
}
accessToken— the upstream provider's access token. Use it as a Bearer token against Google/Microsoft/GitHub APIs directly.expiresIn— seconds until this specific token expires. Never assume the 3600-second default; the value returned here accounts for cache age and refresh recency.scopes— every scope currently associated with the stored token. A superset ofrequiredScopes.clientMetadata— public identifiers that downstream widgets (notably the Google Drive Picker web component) need alongside the access token:clientMetadata.clientId— the OAuth 2.0 client ID Claxx Auth uses for this provider. Publicly visible on any consent screen; safe to expose to the browser.clientMetadata.appId— present only forgoogleproviders. Google Cloud project number derived from the client ID prefix. The Picker widget requires it alongside the OAuth token.- Downstream apps must NOT hold their own copies of these identifiers. They correspond to Claxx Auth's Google Cloud project, not the consuming app's. Re-read them on every broker call.
Security headers on the response are forced to Cache-Control: no-store, private — do not log this body, and do not store the access token beyond request scope.
Encryption and storage
- Upstream access tokens are KMS-encrypted at rest in
accounts.access_tokenand decrypted on demand. - Upstream refresh tokens are KMS-encrypted in
accounts.refresh_tokenand never leave Claxx Auth. They are not exposed to any endpoint. - Decryption uses the same AWS KMS key as other Claxx Auth secrets (client secrets, webhook secrets).
- A per-(user, provider) lock (
provider-refresh:<userId>:<providerSlug>) serializes refresh attempts so concurrent requests don't waste upstream rate-limit quota or lose a rotated refresh token.
Refresh strategy
When a request arrives:
- Claxx Auth looks up the user's
accountsrow for the provider. - If the stored access token is valid for at least 5 more minutes, it's decrypted and returned directly.
- Otherwise, Claxx Auth acquires a refresh lock, re-checks freshness (another thread may have refreshed), and exchanges the stored refresh token at the upstream's token endpoint.
- For Microsoft, which rotates refresh tokens on every exchange, Claxx Auth persists the new refresh token atomically alongside the new access token.
- The new access token is KMS-encrypted, stored, and returned.
If the upstream rejects the refresh token (user revoked at the provider, admin revoked, scope change required, etc.), Claxx Auth returns upstream_reauth_required. Your app should walk the user through Claxx Auth sign-in again with the scopes they need. See error handling below.
Example: Google Drive on behalf of the user
const bluAuthAccessToken = req.session.bluauthAccessToken;
const res = await fetch('https://auth.example.com/api/provider-tokens/google', {
method: 'POST',
headers: {
Authorization: `Bearer ${bluAuthAccessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
requiredScopes: ['https://www.googleapis.com/auth/drive.readonly']
})
});
if (!res.ok) {
const err = await res.json();
if (err.error?.code === 'upstream_reauth_required') {
return redirectToReauth(err.error.provider, err.error.requiredScopes);
}
throw new Error(`provider-token failed: ${err.error?.code}`);
}
const { data } = await res.json();
const drive = await fetch('https://www.googleapis.com/drive/v3/files?pageSize=10', {
headers: { Authorization: `Bearer ${data.accessToken}` }
});
Error handling
All errors use the Claxx Auth standard error envelope:
{
"success": false,
"error": {
"code": "upstream_reauth_required",
"message": "Refresh token has been revoked",
"status": 403
}
}
| Error code | HTTP | Meaning | Required action |
|---|---|---|---|
invalid_token | 401 | Claxx Auth access token is missing, malformed, or expired. | Re-authenticate the user via OIDC. |
unauthorized_client | 403 | The calling client is not allowed to broker tokens for this provider. | Ask an admin to add the provider slug to the client's allowedProviderTokens. |
no_linked_account | 404 | The user has never linked this provider. | Redirect the user through Claxx Auth sign-in with the provider. |
insufficient_scope | 403 | The grant does not include every requiredScope. The error payload includes grantedScopes and requiredScopes. | Redirect user through Claxx Auth sign-in with the additional scopes. |
upstream_reauth_required | 403 | The refresh token is revoked / invalid. Claxx Auth has cleared the grant. | Redirect user through Claxx Auth sign-in. |
upstream_provider_error | 502 | Upstream provider returned an error during refresh. | Log the providerError detail; retry with exponential backoff. Check provider status page before escalating. |
validation_error | 400 | requiredScopes was not an array of strings. | Fix the request. |
Re-authentication flow
When a downstream app receives upstream_reauth_required or insufficient_scope, drive the user back through the OIDC authorize flow with the upstream scopes inlined. Claxx Auth propagates additional_scopes to the upstream provider's consent screen:
GET https://auth.example.com/api/oidc/authorize
?client_id=<your-client>
&redirect_uri=<your-callback>
&response_type=code
&scope=openid%20profile%20email
&state=<opaque>
&nonce=<opaque>
&code_challenge=<...>
&code_challenge_method=S256
&additional_scopes=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.readonly
additional_scopes are upstream scopes. Claxx Auth validates they're in the provider's admin-approved list and then forwards them upstream. On successful consent, the grant record is updated and subsequent /api/provider-tokens/... calls succeed.
What Claxx Auth does NOT do
- Does not expose refresh tokens. Ever. Not via any endpoint, not to any client.
- Does not proxy API calls. Downstream apps still talk directly to Google/Microsoft/etc. with the brokered access token. Claxx Auth is a token source, not a reverse proxy.
- Does not support arbitrary providers. Only providers explicitly configured in
/admin/providerswith refresh-token issuance enabled andallowedProviderTokenspopulated on the client. - Does not support providers that don't return refresh tokens. GitHub's OAuth Apps, for example, issue long-lived access tokens without refresh; those can be brokered but cannot be refreshed — once they expire, the user re-authenticates.
- Does not validate upstream scopes. Claxx Auth trusts the upstream provider to reject malformed scopes at consent time.
Limitations
- Not all providers support offline access. Google requires
access_type=offline&prompt=consentto issue a refresh token; the Claxx Auth Google provider strategy sets these automatically. Microsoft requires theoffline_accessscope. For custom OIDC providers, the admin must configure scopes and prompts correctly. - Scope additions require consent. A scope that wasn't in the original consent won't retroactively appear in the grant — the user must re-authenticate.
- Token revocation is not instantaneous. If the user revokes access at the upstream provider directly (e.g. via Google's third-party apps page), Claxx Auth discovers the revocation on the next refresh attempt and returns
upstream_reauth_required. In-flight access tokens remain valid upstream until they expire.