1. Component layers
The system is composed of five logical layers living in distinct physical pieces.2. How the code is organized
The IdP logic is a cross-cutting module, separate from the standard per-resource pattern of the rest of the API.3. Key architectural decisions (ADR-lite)
ADR-01 — A managed OAuth 2.1 engine as the protocol engine
ADR-01 — A managed OAuth 2.1 engine as the protocol engine
iss/aud and
enforce enablement. Trade-off: we depend on the engine’s capabilities; the
lack of native custom scopes is irrelevant because we do not inject
permissions into tokens.ADR-02 — Full wrap of OAuth endpoints behind the B Brands origin
ADR-02 — Full wrap of OAuth endpoints behind the B Brands origin
ADR-03 (revised) — Canonical issuer = https://auth.bbrands.io (per environment)
ADR-03 (revised) — Canonical issuer = https://auth.bbrands.io (per environment)
iss claim of issued access tokens is the environment’s auth.* URL
(e.g. https://development.auth.bbrands.io in development), rewritten by
the access-token hook. A non-production token is never valid in production
because clients validate iss against their own environment. This revises
the original decision that named api.* as the issuer.ADR-03b (revised) — A single user-facing identity origin: auth.*
ADR-03b (revised) — A single user-facing identity origin: auth.*
auth.* origin serves both the interactive surfaces (login,
consent) and every protocol endpoint (discovery, JWKS, authorize, token,
userinfo, revoke) — the latter through transparent rewrites to the api.*
backend. This is the accounts.google.com shape: one stable identity
origin for clients, while the implementation host behind it stays
replaceable. Boot-time consistency checks and a JWKS availability monitor
guard the contract.ADR-04 — Asymmetric signing (ES256) for OAuth tokens
ADR-04 — Asymmetric signing (ES256) for OAuth tokens
ADR-04b — Per-client RS256 id_token re-signing
ADR-04b — Per-client RS256 id_token re-signing
auth_oidc module) register id_token_alg: "RS256" (the OIDC
id_token_signed_response_alg). For those clients only, the IdP verifies
the genuine ES256 id_token and re-signs it with its own RSA key,
preserving every claim. The JWKS publishes the RSA public key alongside the
EC key, discovery advertises both algorithms, and the access token is
never re-signed. Any re-signing failure falls back to the ES256 original.ADR-05 — The IdP does not inject permissions into tokens
ADR-05 — The IdP does not inject permissions into tokens
sub, email, profile). There is no
permission or role claim. The access-token hook only rewrites iss/aud and
checks that the user is enabled for the client.ADR-06 — A single source of truth for identity
ADR-06 — A single source of truth for identity
ADR-07 — Pre-sync provisioning via webhook (not JIT)
ADR-07 — Pre-sync provisioning via webhook (not JIT)
ADR-08 — Short TTL + blacklist for effective revocation
ADR-08 — Short TTL + blacklist for effective revocation
ADR-09 — OIDC → internal JWT exchange to consume B Brands APIs
ADR-09 — OIDC → internal JWT exchange to consume B Brands APIs
4. The central piece: the access-token hook
The hook does not inject permissions. Its job is to rewriteiss/aud,
validate that the user is enabled for the client and attach the
business-account dimension (https://bbrands.io/profile). If the user is not
enabled, no token is issued (failsafe deny).
5. Per-client isolation
Although every client shares one engine instance, isolation is guaranteed by:- The
https://bbrands.io/client_handleclaim identifying the requesting client in each OAuth access token (audis the fixedbbrands-idpaudience). client_handlevalidation by each client (a token forcrm-prodis not valid in an ERP client).- Enablement via the user × platform access record, checked on issuance, exchange and refresh.
- The audit log filtered by
client_handle.