auth.* issuer origin. No
client-facing endpoint points at the underlying engine — even /.well-known/*
is served from the issuer. Examples below use the production base URL
https://auth.bbrands.io; substitute the per-environment base:
Conventions
- Content-Type:
application/json, except OAuth endpoints that useapplication/x-www-form-urlencoded. - Transport: HTTPS only (TLS 1.2+).
- Caching:
Cache-Control: no-storeon every OAuth endpoint. - Errors: OAuth endpoints return
{"error": "...", "error_description": "..."}. - Correlation: every response carries
X-Request-Id.
Discovery
GET /.well-known/openid-configuration
Returns the OIDC discovery document. Public, no auth. Point your client’s
auto-configuration at this URL.
The
iss in discovery and in every issued access token is the canonical
per-environment auth.* URL — never the underlying engine URL. The
registration_endpoint is intentionally not exposed: dynamic client
registration is off; clients are onboarded through the identity admin console.id_token_signing_alg_values_supported lists ["ES256"] by default and
["ES256", "RS256"] on environments where the RS256 id_token lane is
enabled. ES256 is always the default algorithm; RS256 only applies to
clients registered with id_token_alg: "RS256" (e.g. Odoo).GET /.well-known/jwks.json
Returns the public keys to validate OAuth JWTs. Public. Served by the
IdP (proxy/cache of the engine’s JWKS); the client never sees the engine URL.
Response headers
On environments with the RS256 lane enabled, the document contains the
engine’s EC P-256 key plus the IdP’s RSA public key (both with
kid).
Verifiers resolve keys by kid, so the extra key is transparent for ES256
clients.Authorization endpoint
GET /api/v3/oauth/authorize
Starts the authorization-code flow. Called by the user’s browser after a
redirect from the client. The request is validated strictly, forwarded to the
engine, and the browser is redirected to the consent UI on the same auth.*
origin.
string
required
Must be
code.string
required
The registered OAuth client identifier (UUID provided at onboarding).
string
required
Must exactly match one of the client’s registered redirect URIs.
string
required
Space-separated list. Must include
openid. Supported: openid email profile.string
required
Client anti-CSRF value, echoed back on the redirect.
string
required
PKCE challenge. PKCE is mandatory for every client.
string
required
Must be
S256 (plain is rejected).string
Recommended. ID-token anti-replay value.
302 redirect to https://auth.bbrands.io/oauth/consent?... (the
consent UI). On a valid session and an enabled first-party client, consent
auto-approves and the browser is redirected back to the client with
?code&state.
Validation errors are returned as 400 JSON (no redirect, since the
redirect_uri cannot be trusted before validation):
Consent
Consent UI — https://auth.bbrands.io/oauth/consent
Served by the authentication app (not an API). It validates the B Brands session
(redirecting to login with ?next= when missing), shows the client name and
requested OIDC scopes, and offers Allow / Deny. first_party + enabled users are
auto-approved.
POST /api/v3/oauth/decision
Records the user’s consent decision for audit. It does not continue the
OAuth code flow (the engine does, via the authorize_url).
string
required
Stable client handle, e.g.
erp-prod.string
required
approve or deny.string[]
Requested scopes.
string
User identifier.
client_handle or an invalid decision returns 400.
Token endpoint
POST /api/v3/oauth/token
Exchanges an authorization code for tokens, or refreshes an access token. Called
server-to-server by the client. The gateway proxies the request to the engine,
which runs the access-token hook (enablement check + iss/aud rewrite).
Headers: Content-Type: application/x-www-form-urlencoded and
Authorization: Basic <base64(client_id:client_secret)> (or the secret in the
body for client_secret_post).
aud is the fixed IdP audience bbrands-idp; the requesting client is
identified by https://bbrands.io/client_handle (and client_id). Validate
iss, the ES256 signature via JWKS, and that client_handle matches your
own handle. The id_token keeps the engine’s issuer/audience and is intended
for the nonce check and profile claims; use the access token as the
identity assertion.RS256 id_token lane: clients registered with
id_token_alg: "RS256"
(e.g. Odoo with the OCA auth_oidc module) receive the same id_token
re-signed with the IdP’s RSA key — header alg: RS256 with a kid that
resolves through /.well-known/jwks.json, every claim preserved (nonce,
at_hash, exp, …). The access token is always ES256.UserInfo endpoint
GET /api/v3/oauth/userinfo
Returns the user’s identity claims, enriched with the business-account
dimension. Proxied to the engine’s userinfo.
Headers: Authorization: Bearer <access_token> (required; a missing header
returns 401 invalid_token with WWW-Authenticate: Bearer).
No permissions/roles claim. UserInfo is identity plus tenancy context: the
https://bbrands.io/profile object carries the account linked to the user
through the primary_user role (account), that account’s external system
id (odoo, null until the account is mapped by the migration API) and the
subject (user, mirrors sub).Revocation endpoint
POST /api/v3/oauth/revoke
Invalidates a token (RFC 7009), typically on logout. Adds the token jti to the
local blacklist store and proxies revocation to the engine.
Body (application/x-www-form-urlencoded): token, token_type_hint
(access_token | refresh_token), client_id.
Per RFC 7009 the response is always
200 and opaque, regardless of whether the
token existed.Custom error codes
Beyond standard OAuth errors, B Brands-specific endpoints may return these inerror.code:
Rate limiting
Responses include
X-RateLimit-Limit, X-RateLimit-Remaining,
X-RateLimit-Reset; 429 carries Retry-After.