Identity Providers
oidc-exchange supports providers that use OpenID Connect (OIDC), an identity protocol built on OAuth 2.0. Standard providers such as Google need only configuration. Providers with specific requirements, such as Apple, use dedicated modules. Support for the non-OIDC provider atproto is planned.
Register each provider in a [providers.<name>] configuration block. Clients pass <name> in the provider field of POST /token requests.
Standard OIDC providers (config-only)
Section titled “Standard OIDC providers (config-only)”Add a provider that follows OpenID Connect with a configuration block. The OidcProvider adapter discovers endpoints, caches public keys from a JSON Web Key Set (JWKS), exchanges authorization codes, and validates identity (ID) tokens.
Google example
Section titled “Google example”[providers.google]adapter = "oidc"issuer = "https://accounts.google.com"client_id = "${GOOGLE_CLIENT_ID}"client_secret = "${GOOGLE_CLIENT_SECRET}"scopes = ["openid", "email", "profile"]endpoint_origins = ["https://oauth2.googleapis.com", "https://www.googleapis.com"]At startup, the adapter reads https://accounts.google.com/.well-known/openid-configuration. This document provides the token endpoint, public key URL, and revocation endpoint. The adapter caches keys and refreshes them when their cache lifetime ends.
Why endpoint_origins is there
Section titled “Why endpoint_origins is there”Google publishes its endpoints off its issuer’s origin: the token and revocation endpoints live on oauth2.googleapis.com and the JWKS URI on www.googleapis.com. The service pins, per provider, the set of origins a discovery document is permitted to name: the issuer’s own origin, plus the origins of any endpoint you configure explicitly, plus every origin listed in endpoint_origins (each a bare https://host[:port]). The set is fixed when configuration loads: discovery can confirm which origins this service talks to but can never widen them, so a compromised or hostile discovery document cannot relocate where verification keys are fetched from or where the client secret is posted. If your provider serves an endpoint from an origin that is not pinned, add it to endpoint_origins; while the check runs in warning mode (the shipped default) an undeclared origin logs a structured warning naming the endpoint, the observed origin, and the permitted set, and rejecting such origins outright becomes a later release’s decision. See the configuration reference for the exact syntax.
Adding any standard OIDC provider
Section titled “Adding any standard OIDC provider”To add a new provider, create a [providers.<name>] block with the following fields:
| Field | Required | Description |
|---|---|---|
adapter |
Yes | Must be "oidc" for standard providers |
issuer |
Yes | The provider’s issuer URL (used for OIDC discovery) |
client_id |
Yes | OAuth client ID from the provider |
client_secret |
Cond. | OAuth client secret (use ${VAR_NAME} placeholder). Optional in config; required by providers whose token endpoint expects one |
scopes |
No | Defaults to ["openid"]. Parsed but not sent on the back-channel code exchange (this service does not perform the authorization redirect) |
jwks_uri |
No | Override discovered JWKS URI |
token_endpoint |
No | Override discovered token endpoint |
revocation_endpoint |
No | Override discovered revocation endpoint |
endpoint_origins |
No | Extra origins (bare https://host[:port]) the provider’s discovery document may name beyond the issuer’s origin and configured-endpoint origins; defaults to empty |
email_verified_claim |
No | oidc adapter only. String, no default. Name of a claim to read (bool-or-string coerced) when the token has no email_verified claim of its own, e.g. Entra’s xms_edov. Must be a non-empty string of at most 64 characters. Mutually exclusive with trust_email_verified |
trust_email_verified |
No | oidc adapter only. Boolean, defaults to false. When true, a token whose email_verified claim is absent counts as verified iff it carries a non-empty email string claim. An explicit false is identical to omitting the key. Mutually exclusive with email_verified_claim |
For most Tier 1 providers, issuer, client_id, and client_secret are the fields you set. Endpoint fields are populated from the issuer’s .well-known/openid-configuration at startup. If provided in config, they override the discovered values. If you override an endpoint onto another host, its origin joins the pinned set automatically; use endpoint_origins for extra origins only the discovered document names.
The email-verification overrides apply only when the provider omits email_verified. An explicit true or false always takes precedence. Setting both overrides for one provider causes a startup configuration error. Each provider using an override logs a structured warning at startup to make the changed trust policy visible.
Examples
Section titled “Examples”Microsoft Entra ID:
[providers.entra]adapter = "oidc"issuer = "https://login.microsoftonline.com/${ENTRA_TENANT_ID}/v2.0"client_id = "${ENTRA_CLIENT_ID}"client_secret = "${ENTRA_CLIENT_SECRET}"scopes = ["openid", "email", "profile"]email_verified_claim = "xms_edov"Entra v2.0 ID tokens include email but omit email_verified. Without an override, the service treats the email as unverified and denies sign-in. Set email_verified_claim to Entra’s optional xms_edov claim, which means “email domain owner verified”. The adapter reads it as a boolean or converts a string value when email_verified is absent. A tenant administrator must enable xms_edov on the app registration before Entra includes it.
If your tenant cannot enable xms_edov, remove email_verified_claim and set trust_email_verified = true. This treats a non-empty email claim as verified when email_verified is absent. Some Entra tenant configurations let users change their email claim. Prefer the xms_edov mapping when your tenant supports it.
GitHub (via OIDC-compatible endpoint):
[providers.github]adapter = "oidc"issuer = "https://token.actions.githubusercontent.com"client_id = "${GITHUB_CLIENT_ID}"client_secret = "${GITHUB_CLIENT_SECRET}"scopes = ["openid", "email"]Apple Sign-In
Section titled “Apple Sign-In”Apple requires a signed JSON Web Token (JWT) as the client_secret for token requests. Sign this short-lived token with your private key using the ES256 algorithm and a P-256 key.
Provide your Apple credentials in the configuration. The AppleProvider module generates and signs the client JWT.
Configuration
Section titled “Configuration”[providers.apple]adapter = "apple"client_id = "com.example.app"team_id = "${APPLE_TEAM_ID}"key_id = "${APPLE_KEY_ID}"private_key_path = "/secrets/apple.p8"| Field | Required | Description |
|---|---|---|
adapter |
Yes | Must be "apple" |
client_id |
Yes | Your Apple Services ID (e.g., com.example.app) |
team_id |
Yes | Your Apple Developer Team ID |
key_id |
Yes | The Key ID from your Apple Developer account |
private_key_path |
Yes | Path to the .p8 private key file (P-256/ES256) |
endpoint_origins |
No | Extra origins (bare https://host[:port]) an override endpoint may use beyond appleid.apple.com |
Apple’s endpoint overrides follow the same origin pinning as standard providers. The defaults all live on appleid.apple.com, so an override onto another origin must be declared in endpoint_origins; the issuer itself stays pinned to https://appleid.apple.com regardless.
How the ES256 client JWT works
Section titled “How the ES256 client JWT works”For each token endpoint call, the Apple provider follows these steps:
- It loads the P-256 private key from
private_key_path. - It creates a JWT with fields called claims. These include
iss(team ID),sub(client ID),aud(https://appleid.apple.com),iat(issue time), andexp(expiry). - It signs the JWT with ES256 using the private key.
- It sends the JWT as
client_secretto Apple’s token endpoint.
The rest of the flow (JWKS fetching, ID token validation, discovery) reuses the shared OIDC utilities from the adapters crate.
Getting your Apple credentials
Section titled “Getting your Apple credentials”- Go to the Apple Developer Portal.
- Create a Services ID under Certificates, Identifiers & Profiles (this is your
client_id). - Create a Sign In with Apple key (this gives you a
.p8file, a Key ID, and your Team ID). - The
.p8file is a P-256 private key in PKCS#8 format.
Provider config format reference
Section titled “Provider config format reference”Every provider block follows this general structure:
[providers.<name>]adapter = "<adapter_type>" # "oidc" or "apple" (atproto is planned)# ... adapter-specific fieldsThe <name> is the identifier clients use in POST /token requests. When a request arrives with provider=google, the service looks up the "google" key in its provider registry. An unknown provider returns 400 Bad Request.
The server creates providers from configuration at startup. It selects the provider by name for each request.