Identity Providers
The identity providers Manzano supports for sign-in, how to register your app with each, and how provider claims map to headers and to
ctx.principal.
Prerequisites: Authentication Overview. What you'll learn: Which providers are supported, what to configure in each provider's dashboard, and what format of JWT your backend should expect.
Status: Preview. The list of supported providers and the per-provider configuration surface are firming up. Where this page references a provider or option that isn't yet exposed through
manzano deployorgrove.toml, contact your Manzano admin for the current configuration path.
Supported providers
| Provider | Type | Notes |
|---|---|---|
| Auth0 | External OIDC | The most exercised path; recommended default for new projects. |
| Clerk | External OIDC | Good fit for React/Next.js teams already using Clerk's components. |
| WorkOS | External OIDC | SSO-focused, strong for B2B applications. |
| External OIDC | Sign-in with Google; suitable as a secondary or workspace-scoped option. | |
| Apple | External OIDC | Sign-in with Apple; required on iOS if you offer third-party sign-in. |
| Custom OIDC | External OIDC | Any compliant OIDC provider (Keycloak, Okta, Cognito, your own). |
| Built-in email | Manzano-provided | Passwordless magic-link sign-in. No external IdP. |
All external providers share the same JWT-based flow; the only differences are the registration steps in their dashboards and which claims they issue by default.
Auth0
What you need from Auth0:
- Create an Auth0 Application of type "Regular Web Application."
- Set the Allowed Callback URL to your app's callback:
https://<your-app>.mzno.me/auth/callback. - Enable the
openid,profile, andemailscopes under "Connections" or your tenant's default scopes. - Copy the Domain, Client ID, and Client Secret from the application's Settings page.
Per-tenant configuration fields:
| Field | Value |
|---|---|
issuer_url | https://<your-auth0-domain>/ (trailing slash required) |
client_id | Auth0 application's Client ID |
client_secret | Auth0 application's Client Secret (stored encrypted; see Credential Store) |
scopes | ["openid", "profile", "email"] |
cookie_name | Cookie name for the ID token (default mzno_id; change if you run multiple tenants on the same apex domain) |
allowed_redirect_paths | List of paths that redirect_uri is allowed to match on post-login redirect |
Testing the configuration: visit /auth/login on your app. You should be redirected to Auth0's Universal Login. After signing in, you should land back on your app with ctx.principal.id set to Auth0's sub claim (typically auth0|<user-id> or google-oauth2|<id>).
Clerk
What you need from Clerk:
- In the Clerk dashboard, open your application's OAuth settings.
- Add your app's callback URL:
https://<your-app>.mzno.me/auth/callback. - Use the Frontend API URL as the
issuer_url(formathttps://clerk.<your-domain>orhttps://<slug>.clerk.accounts.devfor development instances).
Per-tenant configuration fields: same shape as Auth0. Use the Frontend API URL as issuer_url.
WorkOS
What you need from WorkOS:
- Configure a WorkOS SSO connection (SAML, OIDC, or one of the pre-built providers).
- Use
https://api.workos.com/as theissuer_url. - Supply the WorkOS API Key as
client_secretand the Client ID asclient_id.
WorkOS returns additional claims (connection_id, organization_id) useful for B2B tenancy — these are forwarded as claims on ctx.principal.claims rather than headers by default.
Use Google's OAuth 2.0 client credentials. issuer_url is https://accounts.google.com. Register your app's callback URL in the Google Cloud console. Google's sub claim is a stable numeric user ID, not an email address — treat it as opaque.
Suitable as a secondary sign-in option on a project; for a Google-Workspace-scoped product, consider enforcing the hd (hosted domain) claim in an authorize hook.
Apple
Use Sign in with Apple. issuer_url is https://appleid.apple.com. Requires an Apple Developer account and a Services ID registered for your app. Apple is the most constrained of the listed providers: the email claim is sometimes a private-relay address, and the email_verified signal can be absent on repeat sign-ins. Plan your user-key strategy around sub, not email.
Custom OIDC
Any OIDC-compliant provider works. Requirements:
- Publishes a JWKS document at
<issuer>/.well-known/jwks.json(or a URL you can paste in explicitly). - Supports PKCE for the authorization-code flow.
- Issues
sub,iss,aud,iat, andexpclaims.
Supply the issuer URL, client ID, and client secret. The edge validates every incoming token against the provider's JWKS, caching keys with a bounded LRU to minimize latency.
Example — Keycloak:
| Field | Value |
|---|---|
issuer_url | https://keycloak.example.com/realms/<realm> |
client_id | Keycloak client ID |
client_secret | Keycloak client secret |
scopes | ["openid", "profile", "email"] |
Built-in email magic link
Manzano includes a passwordless email flow you can enable without configuring an external IdP. Users enter their email on the sign-in page, receive a one-time link, click it, and are signed in.
What you get: an OIDC-compatible identity service that issues tokens signed with Manzano-managed keys. From the edge and backend's perspective, magic-link tokens look identical to tokens from an external IdP.
Per-tenant configuration fields:
| Field | Default | Meaning |
|---|---|---|
jwt_issuer | https://auth.mzno.me | Issuer used in the minted JWT |
token_ttl_secs | 600 (10 min) | How long a magic link is valid before it expires |
cooldown_secs | 60 | Minimum interval between magic-link requests for the same email |
jwt_expiry_secs | 86400 (24 h) | Lifetime of the signed-in session |
email_sender | Manzano-hosted | The email transport (bring-your-own via SES/SendGrid if self-hosting) |
What ctx.principal looks like. For magic-link sign-ins, ctx.principal.id is the user's email prefixed with email| (e.g. email|user@host.com). ctx.principal.claims.email is the bare email address. ctx.principal.claims.auth_method is "email", which lets an authorize hook distinguish magic-link sessions from OIDC sessions.
Cloudflare Turnstile bot protection is available on the magic-link flow. When enabled for a tenant, the sign-in page renders a Turnstile widget and the server verifies the token before accepting the email submission. Turnstile configuration is currently set out-of-band by Manzano operators; contact your admin if you want Turnstile on your project.
JWT shape
Regardless of provider, the JWT that reaches the edge should look like:
{
"sub": "auth0|abc123",
"iss": "https://your-domain.auth0.com/",
"aud": "<your-client-id>",
"iat": 1735689600,
"exp": 1735693200,
"email": "user@host.com",
"email_verified": true
}
The built-in email provider adds "auth_method": "email". WorkOS adds "connection_id" and "organization_id". Any other provider-specific claims pass through to ctx.principal.claims unchanged.
Claim-to-header mapping
The edge extracts claims from the JWT and places them into HTTP headers before forwarding the request to your backend. The defaults are:
| JWT claim | HTTP header | ctx.principal path |
|---|---|---|
sub | X-Sub | ctx.principal.id |
email | X-Email | ctx.principal.claims.email |
email_verified | X-Email-Verified | ctx.principal.claims.email_verified |
See Identity Flow for the full set of headers (X-Site-Id, X-Project-Id, X-Distribution-Id, and others) and for the HMAC signature that protects them.
Custom claims you want to surface in your backend can be added to the claim-to-header mapping. The runbook for doing this is evolving; until it stabilizes, the safe path is to keep custom claims in the JWT and access them via ctx.principal.claims.<name> directly — the edge forwards the full claim set.
See Also
- Authentication Overview — the three-layer mental model
- Enabling Authentication — step-by-step to turn on login
- Identity Flow — edge headers, HMAC trust boundary, mapping to
ctx.principal - Credential Store — where
client_secretvalues live - Authorization Hooks — what to do once a user is authenticated