Skip to main content
OIDC Federation lets a parent organization register its own OIDC Identity Provider (an internal auth service, an Auth0/Cognito tenant, or any standards-compliant IdP) as a trusted issuer for its sub-organization users. It’s the recommended path for customers who already have a user system and want to keep their IdP as the source of truth for identity while still getting cryptographically-verified, non-custodial logins into 0xkey.
This page is the task-oriented integration guide. For the conceptual background and how federation compares to the built-in social providers (Google, Apple, …), see Social logins → Custom OIDC providers. The mechanics below reuse the same enclave verification path described there.

When to use federation

Use OIDC Federation

You operate your own IdP (or an Auth0/Cognito/Okta tenant) and want it to be the identity source of truth. You want to onboard existing users into embedded wallets without asking them to re-authenticate through a third-party social button.

Use built-in social logins

You just want “Sign in with Google/Apple/…”. Those issuers are trusted by default — no registration needed. See Social logins.
Federation and built-in social logins are not mutually exclusive: a single sub-organization user can have both a Google identity and your own IdP identity bound to them.

How it fits together

Trust is established once, at the organization level; identity binding and login happen per user. The enclave never trusts the token at face value: on every login or binding it independently fetches the issuer’s discovery document and JWKS over HTTPS (through the TLS Fetcher) and re-verifies the signature, iss, aud, exp, and nonce.

Prerequisites for your IdP

Your Identity Provider must expose the standard OIDC discovery surface:
  • OIDC Discovery at https://<your-issuer>/.well-known/openid-configuration, returning at minimum issuer and jwks_uri.
  • JWKS served live over HTTPS at the discovered jwks_uri. 0xkey enforces full public-CA TLS validation — there is no self-signed / plain-HTTP / private-CA bypass, even in dev.
  • Same-host jwks_uri. For self-hosted (non-built-in) issuers, the jwks_uri host must match the issuer host. A mismatch is rejected (see JWKS host binding).
  • RS256 or ES256 signatures. Other algorithms are rejected.
  • Stable sub. The sub claim must be a stable, non-reassignable per-user identifier — (iss, sub) is 0xkey’s identity primary key. If your IdP allows account merging or sub reassignment, review it carefully (see the Auth0 caveat).
  • Key rotation grace period. When you rotate signing keys, publish the new key and keep the old key in the JWKS until in-flight tokens expire. 0xkey simply trusts whatever the JWKS currently serves.

Integration guide

1

Register your issuer (one-time, root-quorum)

Registering an issuer establishes trust for the whole parent organization. It is a root-quorum-only operation.From the Dashboard: go to Wallet Kit → OIDC Providers → Add provider, fill in a name, the issuer URL, and at least one audience. The UI wraps the same activity behind a passkey-gated flow.Or via the API with CREATE_OIDC_PROVIDER:
cURL — CreateOidcProvider
  • issuer must exactly match the iss claim of your IdP’s tokens, and is immutable — delete and re-create if it ever changes.
  • audiences is a required, non-empty allowlist of aud values (your OAuth2 client IDs). There is no wildcard — see fail-closed audiences.
2

Mint an id_token bound to the session key

Before starting your IdP login flow, the frontend generates an API keypair (this becomes the session key). When it authenticates the user against your IdP, it sets the OIDC nonce to sha256(publicKey) so the resulting id_token is cryptographically bound to that key.If your IdP cannot customize nonce, 0xkey also accepts a tknonce claim — only one of the two needs to equal sha256(publicKey). Registration-only flows (CREATE_OIDC_PROVIDER, CREATE_SUB_ORGANIZATION) don’t carry a publicKey, so the nonce binding is not enforced there.
3

Onboard or log the user in

With the id_token and the API publicKey, your backend:
  1. Looks up the sub-organization(s) associated with that identity.
  2. If none exists, creates one with CREATE_SUB_ORGANIZATION, seeding the user’s OAuth provider from the token.
  3. Calls oauth_login with the id_token + publicKey to obtain a session JWT.
Once your issuer is registered, this flow is identical to a built-in provider — oauth_login doesn’t care whether the issuer is Google or your own IdP.
4

Bind additional identities to an existing user (optional)

To add your IdP as a login method for a user who already exists (e.g. they signed up with a passkey), submit CREATE_OAUTH_PROVIDERS with the raw id_token:
cURL — CreateOauthProviders
Send oidcToken as a flat string field inside each oauthProviders entry, exactly as shown. Do not nest it under tokenOrClaims. The enclave re-verifies this token end-to-end before binding the identity. See missing field oidcToken if you hit a deserialization error.

Managing the registry

All three write operations are root-quorum-only.

Security model

Registering an issuer is a trust decision, not a convenience setting. Anyone who can mint a validly-signed token for a registered (issuer, audience) pair can log in as — or bind an identity to — any matching sub-organization user. Keep the audience list as narrow as possible and restrict CREATE_/UPDATE_/DELETE_OIDC_PROVIDER to root-quorum admins.
  • Fail-closed audiences. An empty audience list is rejected outright; there is no “trust this issuer for any audience” option.
  • JWKS host binding. For federated (non-built-in) issuers, the JWKS fetch host must match the issuer host — this blocks a tampered discovery document from redirecting key-fetching to an attacker-controlled host.
  • Live verification. Signature, iss, aud, exp, nbf (with 60s skew), and nonce are checked inside the enclave against the issuer’s live JWKS on every login/binding — nothing is cached as “already trusted”.

Troubleshooting

The verification errors below surface from the enclave during oauth_login, CREATE_SUB_ORGANIZATION, or CREATE_OAUTH_PROVIDERS. They are all fail-closed — a rejected token never results in a session or a bound identity.

OIDC issuer ’…’ is not a registered/enabled trusted IdP for this organization

Cause: the token’s iss doesn’t match any enabled provider registered for the organization. Fix:
  • Confirm you ran CREATE_OIDC_PROVIDER for this issuer against the correct parent organization, and that the provider is enabled.
  • The registered issuer must be byte-for-byte equal to the token’s iss (watch for a trailing slash, http vs https, or a tenant path). The issuer is immutable — if it’s wrong, delete and re-create.
  • Verify the registry with GET_OIDC_PROVIDERS.

JWT ‘iss’ mismatch: expected ’…’, got ’…’

Cause: the token’s issuer differs from the provider’s configured issuer. Fix: align your IdP’s iss claim with the exact string you registered. These must match character-for-character.

JWT ‘aud’ mismatch: expected one of […]

Cause: the token’s aud isn’t in the registered audience allowlist. Fix: add the OAuth2 client ID your app uses to the provider’s audiences via UPDATE_OIDC_PROVIDER. If you use different client IDs per platform (web/iOS/Android), register each one you intend to accept — or standardize on a single client ID (see the aud note).

JWT ‘nonce’ does not match sha256(public_key)

Cause: the token’s nonce (or tknonce) isn’t the SHA-256 of the API publicKey used in oauth_login. Fix: set the OIDC nonce to sha256(publicKey) when you start the IdP login flow, using the same keypair you later pass to oauth_login. This binding is enforced only during authentication, not during registration-only flows.

JWT has expired

Cause: the id_token’s exp is in the past by the time the enclave verifies it. Fix: OIDC id_tokens are short-lived (often ~1 hour). Mint a fresh token immediately before the 0xkey call rather than reusing a stale one.

JWKS host does not match issuer host

Full message: OIDC JWKS fetch URL host does not match issuer host: issuer='…', jwks_url='…'. Cause: for a federated issuer, the jwks_uri in your discovery document points at a different host than the issuer. Fix: serve your JWKS on the same host as your issuer. (Built-in social providers like Google are exempt because they legitimately publish JWKS on a separate host; this check applies only to your own registered issuers.)

Unsupported JWT algorithm / JWK not found for kid

Cause: the token is signed with an unsupported algorithm (only RS256 and ES256 are accepted), or its kid isn’t present in the currently-served JWKS. Fix: sign tokens with RS256 or ES256, and keep every currently-valid signing key published in your JWKS. During rotation, retain the previous key until all tokens signed with it have expired.

invalid OIDC token

Cause: malformed JWT (not three .-separated parts) or missing a required claim (aud, sub, iss). Fix: see invalid OIDC token in the error reference.

missing field oidcToken

Cause: a CREATE_OAUTH_PROVIDERS request whose oauthProviders[] entries don’t carry a top-level oidcToken string. Fix: put oidcToken as a flat field on each provider object (as in the binding example above), not nested under tokenOrClaims. The enclave expects the raw id_token string per provider entry.

Social logins

Built-in providers, OAuth 2.0-only wrapper flow, and the full OIDC verification model.

Create OIDC provider

API reference for registering an issuer.

Create OAuth providers

API reference for binding an identity to a user.

Errors

Full error-code reference and troubleshooting hub.