Federated identity
Everything in the rest of this site treats auth-server as your app’s login backend: your app holds the credentials, your app gets the token. This section is about the other direction — other sites asking this auth-server who somebody is, the way a page asks Google or Apple.
The server is an identity provider. A relying party (RP) — a site you may not own, on an origin you do not control — sends a user here, the user consents, and the RP gets back an identity assertion signed by this server.
Three ways to ask
| FedCM | OIDC | Password | |
|---|---|---|---|
| Who drives it | The browser | Your page (redirect or popup) | Your page |
| User sees | A native browser sheet | This server’s login page | Your own form |
| Credentials cross origins | No | No | Yes — RP handles the password |
| Works today in | Chrome · Edge | Every browser | Every browser |
| Needs a registered client | Yes | Yes | An app_code |
| Good for | A one-tap “Sign in with …” on a third-party site | The universal fallback; server-side RPs | Your own first-party apps |
They are not alternatives you pick once. A well-built “Sign in with {Provider}” button tries FedCM, falls back to OIDC, and only offers a password form if the RP is first-party. That exact sequence is written out in Sign in with {Provider}.
What they share
FedCM is not a second identity system. It is a new way for a browser to ask the same question, answered from the same place:
┌───────────────────────────────┐ FedCM ──────▶│ │ │ one signing key (RS256) │ OIDC ──────▶│ one JWKS │──▶ id_token │ one OIDC client registry │ ven CLI ─────▶│ one session cookie │ / admin API └───────────────────────────────┘- Same keys. The FedCM assertion is an
id_tokensigned with the same RSA key published at/.well-known/jwks.json. - Same client registry. A FedCM
client_idis an OIDCclient_id— the row created byPOST /admin/oauth/clients. There is no separate FedCM registration step. - Same origin policy. The origins FedCM will talk to are derived
from that client’s registered
redirect_uris. Register the RP once and it works over both. - Same session. If the user already has a session with this server, both paths use it.
So: adding FedCM to an RP that already does OIDC is a client-side change. There is nothing new to register.
One provider per installation
This server is the provider. It does not host a directory of
providers and there is no per-tenant provider switch — the name, icon
and colours it publishes at
/api/v1/provider describe
this deployment.
A deployment for “Test Organization Name” publishes itself as
TestOrgName; the CivicGate deployment publishes itself as CivicGate.
Two providers means two installations.
Two token families (don’t confuse them)
The single most common integration mistake. Both are JWTs; they are not interchangeable.
| First-party access token | OIDC id_token | |
|---|---|---|
| Issued by | /auth/login, /auth/refresh | /oauth/token, FedCM assertion |
| Algorithm | HS256 | RS256 |
| Verified with | The shared JWT_ACCESS_SECRET | The public key from /.well-known/jwks.json |
| Audience | Your app_code | The RP’s client_id |
| Who may hold the verifying key | First-party services only | Anyone — it is public |
| Purpose | Authorize API calls | Assert who the user is, once |
The id_token is a statement about the user, not an API credential.
An RP verifies it, creates its own session, and throws it away. To call
this server’s API on the user’s behalf, exchange for an access token
through the token endpoint.
The HS256 secret is shared with first-party services because they are first-party. Federation exists precisely so that a non-first-party site never needs it.
In this section
- OIDC provider — discovery, JWKS, authorization code + PKCE, the exact-match redirect rule, and registering a client.
- FedCM — the browser API, its five endpoints, the two request conditions that trip everybody up, and the limits worth knowing before you build on it.
- Provider identity — the public document an RP reads to render a correct “Sign in with …” button, and the env vars that fill it.
- Sign in with {Provider} — one worked integration covering all three paths.