Admin
Connecting an Identity Provider
Wire Auth0, Clerk, Okta, or any OIDC provider so Alter can resolve users from their JWTs — and sync the full user directory.
The identity provider (IDP) is the external service that authenticates the app’s end users. When one is configured, Alter verifies the JWTs the IDP issues, resolves which user is making each call, and looks up that user’s grants automatically.
One IDP per app. Skip this page for backend-only apps that never see end users.
What gets configured
Section titled “What gets configured”Four layers, each optional after the first:
- JWT verification (required) — issuer URL, JWKS URL, audience, claim mappings.
- OIDC sign-in (optional) — adds a sign-in flow for
app.authenticate()and the Wallet. - Webhook deprovisioning (optional) — real-time revocation when the IDP deletes or suspends a user, removes a group member, or deletes a group.
- Directory sync (optional) — bulk-imports users and groups from the IDP’s management API, then keeps them reconciled on a schedule.
Supported providers
Section titled “Supported providers”| Provider | Path |
|---|---|
| Auth0, Clerk, Okta | First-class — pick the tab below. |
| Other standards-compliant OIDC providers (OneLogin, Ping, Stytch, JumpCloud, …) | Use Custom OIDC — supply issuer, JWKS, audience, claim mappings. |
| Keycloak, Microsoft Entra ID, Amazon Cognito, Google, Firebase Auth, Supabase Auth | Broker through Auth0, Clerk, or Okta. Direct integration is rejected at validation time. |
How directory sync works
Section titled “How directory sync works”Without directory sync, Alter learns about a user the first time that user’s JWT arrives (just-in-time provisioning). Directory sync mirrors the IDP’s entire directory ahead of time, so users and groups exist in Alter before anyone signs in — which is what makes group-bound grants assignable to people who haven’t authenticated yet, and what offboards users who will never send another JWT.
Webhooks and directory sync are complementary, not alternatives:
| Webhooks | Directory sync | |
|---|---|---|
| Latency | Real-time, per event | Scheduled — every 6 hours, plus on-demand |
| Coverage | Only events the IDP emits after setup | The full directory, including pre-existing users |
| Offboarding | Immediate on a delete/deactivate event | Users absent from the directory are deprovisioned on the next sync |
| Credential | Webhook signing secret | Management-API credential (read-only) |
Directory sync needs a management credential — a read-only credential for the IDP’s management API, created in the IDP’s own console and pasted into the Alter portal (the app’s Identity page → the identity provider card → Directory Import). The portal shows the same per-provider walkthrough inline under “How to set up directory import”.
What each provider supports:
| Provider | Users synced | Groups synced | Group grants |
|---|---|---|---|
| Okta | Yes | Yes — Okta groups + memberships | Yes — requires Group claim carries stable IDs on the IDP config |
| Clerk | Yes | Yes — Clerk organizations + memberships | Yes |
| Auth0 | Yes | No | Not available |
| Custom OIDC | Not available | Not available — OIDC defines no management API | Not available |
Once a credential is saved, the first import can be triggered immediately with Run directory import; after that, Alter re-imports the full directory automatically every 6 hours. Each run’s outcome (counts, last error) is visible in the same portal section, and every imported, updated, or deprovisioned user lands in the provisioning audit trail.
Setup by provider
Section titled “Setup by provider”1. JWT verification
Section titled “1. JWT verification”In the Auth0 Dashboard:
- Pick the API or application whose JWTs Alter should accept.
- Note the Issuer URL and Audience. Auth0 issuers vary by tenant region:
https://<tenant>.us.auth0.com/(US — default for new tenants)https://<tenant>.eu.auth0.com/(EU)https://<tenant>.au.auth0.com/(AU)https://<tenant>.jp.auth0.com/(JP)https://<tenant>.auth0.com/(legacy / no region prefix)- The tenant’s custom-domain URL when a custom domain is configured.
In the Alter portal → app’s Identity page:
- Add Identity Provider → paste the issuer URL → Discover. Auth0 is recognized; claim mappings pre-fill.
- Paste the audience.
- Optional — to record each user’s group memberships (for visibility in the portal): add an Auth0 Action emitting a
https://alter.dev/groups(or any namespaced) claim on the access token, and map it in the portal before the first sign-in. Note that group grants are not available with Auth0 regardless of this claim — Auth0 sends no group lifecycle webhooks, so the claim records memberships but cannot back a group-bound grant. - Add Provider.
2. OIDC sign-in (optional)
Section titled “2. OIDC sign-in (optional)”Required only for app.authenticate() or the Wallet sign-in flow.
In Auth0: Applications → Create Application → Regular Web Application. Paste both Alter redirect URIs into Allowed Callback URLs (comma-separated). Paste the Wallet logout URL (bare URL, no query params) into Allowed Logout URLs. Copy Client ID and Client Secret.
In Alter: User Authentication (OIDC) section → paste credentials → Save.
3. Webhook deprovisioning (optional)
Section titled “3. Webhook deprovisioning (optional)”In Alter: Webhooks → Enable → copy the signing secret and endpoint URL (shown once).
In Auth0: Monitoring → Streams → Create Stream → Custom Webhook. Payload URL = the Alter endpoint. Authorization Token = the signing secret (no Bearer prefix — Auth0 adds it). Set Content Type to application/json and Content Format to JSON Object. Filter by Event and subscribe only to these codes: sdu (Success User Deletion → deprovision) and ublkdu (anomaly-detection login block released → reactivates a previously-suspended user). Do not subscribe to sul — it fires on every successful login and would flood the receiver. Auth0 publishes no log code for “admin manually blocked user”, so suspension is not auto-synced from Auth0 today; lifecycle deprovisioning is reliable for explicit user deletes.
4. Directory sync (optional)
Section titled “4. Directory sync (optional)”Syncs users only — Auth0 groups are not imported, and group grants are not available for Auth0-backed apps (see How directory sync works). Blocked Auth0 users sync as suspended; deleted users are deprovisioned on the next run.
In Auth0:
- Applications → Create Application → Machine to Machine Applications.
- Authorize the new application for the Auth0 Management API and grant it only the
read:userspermission. - Copy the tenant Domain, Client ID, and Client Secret from the application’s Settings tab.
In Alter: the identity provider card → Directory Import → paste Domain, Client ID, and Client Secret → Save credential. The credential is verified against the tenant before it is stored; the first import can be triggered immediately with Run directory import.
Common pitfalls
Section titled “Common pitfalls”- Custom domain mismatch — JWTs use the custom domain but the portal has the tenant URL (or vice versa). Match exactly what’s in the JWT
issclaim. - Expecting group grants to work — they don’t on Auth0 (no group lifecycle webhooks; see the support table). The group claim only records memberships for visibility. Bind grants to users or to the app (system) instead.
- Wrong Management API permission — a Machine-to-Machine app without
read:usersis rejected when the credential is saved. Grant the permission under the application’s APIs tab, not on a different API.
1. JWT verification
Section titled “1. JWT verification”In the Clerk Dashboard: note the instance URL (https://<app>.clerk.accounts.dev for dev, https://clerk.<custom-domain> for production). That’s the issuer.
In the Alter portal → Identity → Add Identity Provider → paste the issuer → Discover. Clerk is recognized; the user-ID claim pre-fills (sub = Clerk user ID).
For group grants (groups = Clerk organizations): the group claim must carry the active organization’s ID (org_…) — that ID is what Clerk’s organization webhooks carry, so login and webhook resolve the same group and real-time revocation works. Clerk’s default v2 session token nests organization data under o (o.id, o.rol), which is not a top-level claim path, so configure a Clerk JWT template (Clerk Dashboard → JWT Templates) emitting it as a top-level claim — e.g. "groups": "{{org.id}}" — and enter that claim name in the Group Claim field before the first sign-in (claim mappings lock after first authentication). Add Provider.
2. OIDC sign-in (optional)
Section titled “2. OIDC sign-in (optional)”Requires a production Clerk instance. In Clerk Dashboard: navigate to OAuth applications → Add OAuth application. Select scopes openid, profile, and email. Add both Alter redirect URIs. Copy the Client Secret from the modal immediately (Clerk does not store it and cannot show it again) and the Client ID from the application’s settings page. Paste both into the Alter portal.
3. Webhook deprovisioning (optional)
Section titled “3. Webhook deprovisioning (optional)”Clerk needs the webhook URL first. In Alter: note the Webhook URL; do not click Enable yet.
In Clerk: Webhooks → Add Endpoint → paste the Alter URL. Subscribe to exactly these four events (others are ignored by the receiver and only add delivery noise):
| Clerk event | What Alter does with it |
|---|---|
user.deleted | Deprovisions the user and revokes their grants |
user.updated | Keeps the user’s email / display name in sync |
organizationMembership.deleted | Revokes group-grant access the moment a member leaves an organization |
organization.deleted | Handles a deleted organization (group) — revokes its group grants |
Copy the Signing Secret (whsec_...). Back in Alter: toggle Enable Webhooks → paste the secret → Save.
4. Directory sync (optional)
Section titled “4. Directory sync (optional)”Syncs users, organizations (as groups), and organization memberships. Banned Clerk users sync as suspended; deleted users are deprovisioned on the next run.
In Clerk: Configure → API keys → copy the Secret key (sk_live_… on production instances, sk_test_… in development).
In Alter: the identity provider card → Directory Import → paste the Secret key → Save credential. The key is verified against the Clerk Backend API before it is stored; the first import can be triggered immediately with Run directory import.
Common pitfalls
Section titled “Common pitfalls”- Dev vs production instance — OIDC sign-in requires production. JWT verification works in either, but issuer URLs differ.
- Custom JWT template — ensure
substill carries the user ID; override the user-ID claim mapping in the portal before first sign-in if needed. - Publishable vs secret key — directory sync needs the Secret key (
sk_…), not the publishable key (pk_…). The publishable key is rejected at save time.
1. JWT verification
Section titled “1. JWT verification”In the Okta Admin Console → Security → API → Authorization Servers → pick the server (default is default). Note the Issuer URI and Audience. The host varies by tenant type:
https://<org>.okta.com/oauth2/default— standard production tenants (incl. trial-* and integrator-*)https://<org>.okta-emea.com/oauth2/default— EMEA-region tenantshttps://<org>.oktapreview.com/oauth2/default— preview / dev tenants
For group-based authorization: edit the authorization server → Claims → add a claim — name groups, include in Access Token, value type Groups, filter Matches regex .*. This emits group names, which is enough for membership visibility.
To use group grants, the claim must instead carry stable group IDs (00g…): configure the claim with an Expression that returns group IDs (Okta’s group functions with "group.id", e.g. getFilteredGroups), and tick Group claim carries stable IDs when adding the provider in Alter. Group grants on an Okta IDP without stable-ID keying are rejected — the IDs in Okta’s group webhooks would never match name-keyed groups, which would break real-time revocation.
In the Alter portal → Identity → Add Identity Provider → paste the issuer URI → Discover. Okta is recognized. Paste the audience. Map the group claim (and set Group claim carries stable IDs if using group grants) before the first sign-in — claim mappings lock after first authentication. Add Provider.
2. OIDC sign-in (optional)
Section titled “2. OIDC sign-in (optional)”In Okta: Applications → Create App Integration → OIDC → Web Application. Sign-in redirect URIs = both Alter redirect URIs. Sign-out redirect URIs = the Wallet logout URL. Assign to the groups whose users should be able to sign into Alter Wallet. Copy Client ID and Client Secret. Paste into the Alter portal.
3. Webhook deprovisioning (optional)
Section titled “3. Webhook deprovisioning (optional)”Okta owns secret generation: you set the Event Hook secret in the Okta Admin Console, then paste the same value into Alter. The order matters — clicking Enable in Alter opens a paste dialog expecting the value Okta gave you, not a generated secret.
In Alter: note the Webhook URL shown on the Identity Provider detail page; do not click Enable yet.
In Okta: Workflow → Event Hooks → Create Event Hook. URL = the Alter webhook URL. Authentication field = Authorization. Set the secret to a value you control and copy it — Okta does not retrieve it for you later. Subscribe to these events:
| Okta event | What Alter does with it |
|---|---|
user.lifecycle.delete.initiated | Deprovisions the user and revokes their grants |
user.lifecycle.deactivate | Deprovisions the user and revokes their grants |
user.lifecycle.suspend | Suspends the user |
user.lifecycle.unsuspend, user.lifecycle.reactivate, user.lifecycle.activate | Restore a previously-suspended user to active |
group.user_membership.remove | Revokes group-grant access the moment a member is removed from a group |
group.lifecycle.delete | Handles a deleted group — revokes its group grants |
Verify and Activate the Event Hook — Okta sends a GET challenge to the Alter URL automatically.
Back in Alter: toggle Enable Webhooks → paste the same secret value → Save.
4. Directory sync (optional)
Section titled “4. Directory sync (optional)”Syncs users, Okta groups, and group memberships — including users in DEPROVISIONED state, so offboarding done in Okta is mirrored. Suspended Okta users sync as suspended; deactivated (DEPROVISIONED) users are deprovisioned in Alter.
In Okta:
- Applications → Create App Integration → API Services.
- In the new app: General → Client Credentials → Edit → set Client authentication to Public key / Private key → Add key → Generate new key. Copy the private key in JWK (JSON) format — Okta shows it only once.
- Okta API Scopes tab → grant
okta.users.readandokta.groups.read. - Admin roles tab → assign the Read-only Administrator role. Okta rejects management-API calls from API service apps that hold no admin role.
In Alter: the identity provider card → Directory Import → paste the Org URL (https://<org>.okta.com), the app’s Client ID, and the private key JWK → Save credential. The Key ID (kid) field is only needed when the pasted JWK omits its own kid. The credential is verified against Okta before it is stored; the first import can be triggered immediately with Run directory import.
Common pitfalls
Section titled “Common pitfalls”- Custom Authorization Server — issuer URI is
https://<org>.okta.com/oauth2/<server-id>, not the org URL. - Missing group claim — Okta emits no groups by default. Without the claim, group memberships are not recorded and group-bound grants don’t work.
- Group claim emits names, not IDs — group grants require stable group IDs in the claim plus the Group claim carries stable IDs setting (see step 1). A name-emitting claim still records memberships but cannot back a group grant.
- Group webhook events not subscribed — without
group.user_membership.removeandgroup.lifecycle.deleteon the Event Hook, group-grant revocation waits for the next directory sync instead of firing in real time. - Audience mismatch — default is
api://default; JWTs with a different audience fail verification. - Missing admin role on the API service app — token exchange succeeds but every directory read returns 403, so the credential is rejected at save time. Assign Read-only Administrator to the app itself (not to a user).
- Public key pasted instead of private — the JWK must contain the private parts (
d,p,q, …). The public half from the key dialog cannot sign the client assertion and is rejected at save time.
For any OIDC-compliant provider not listed above (OneLogin, Ping, Stytch, JumpCloud, etc.). Providers in the brokered-only row of the support table — Keycloak, Microsoft Entra ID, Amazon Cognito, Google, Firebase Auth, Supabase Auth — are rejected at validation time by Custom OIDC and must instead be brokered through Auth0, Clerk, or Okta using the broker’s enterprise-connection feature.
1. JWT verification
Section titled “1. JWT verification”Find these from the provider’s documentation:
- Issuer URL (the value of the
issclaim). - JWKS URL (usually
<issuer>/.well-known/jwks.jsonper OIDC discovery). - Audience (the value of the
audclaim). - User-ID claim (default
sub). - Group claim (provider-specific — common values:
groups,cognito:groups,roles).
In the Alter portal → Identity → Add Identity Provider → paste the issuer URL → Discover. When OIDC discovery is exposed at <issuer>/.well-known/openid-configuration, Alter pulls JWKS automatically. Override claim mappings when the JWT uses non-standard names. Set group / role claims before any user signs in — they lock after first authentication.
2. OIDC sign-in (optional)
Section titled “2. OIDC sign-in (optional)”Create an OIDC application (often called “Web App” or “Confidential Client”) at the provider. Add both Alter redirect URIs as callbacks. When the provider supports post_logout_redirect_uri, add the Wallet logout URL. Paste Client ID and Client Secret into the Alter portal.
3. Webhook deprovisioning (optional)
Section titled “3. Webhook deprovisioning (optional)”In Alter: Webhooks → Enable → copy the secret and endpoint. In the IDP, configure a webhook subscribing to user-deleted / user-disabled events with the secret as an Authorization header.
When the IDP supports no webhooks, deprovisioning waits until the deleted user’s JWT expires.
4. Directory sync
Section titled “4. Directory sync”Not available for Custom OIDC — OIDC standardizes authentication, not a management API, so there is no directory to read. Users are provisioned just-in-time on first sign-in; offboarding relies on webhooks (when the provider has them) or JWT expiry.
Common pitfalls
Section titled “Common pitfalls”- Brokered-only providers fail at save time. Issuer URLs from Keycloak, Entra, Cognito, Google (
accounts.google.com), Firebase, or Supabase are rejected. Use the broker pattern instead (Auth0 / Clerk / Okta with an enterprise connection). - Audience formatting — some providers send a string, some an array. Alter handles both; the portal value must match an entry.