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 deploy or grove.toml, contact your Manzano admin for the current configuration path.


Supported providers

ProviderTypeNotes
Auth0External OIDCThe most exercised path; recommended default for new projects.
ClerkExternal OIDCGood fit for React/Next.js teams already using Clerk's components.
WorkOSExternal OIDCSSO-focused, strong for B2B applications.
GoogleExternal OIDCSign-in with Google; suitable as a secondary or workspace-scoped option.
AppleExternal OIDCSign-in with Apple; required on iOS if you offer third-party sign-in.
Custom OIDCExternal OIDCAny compliant OIDC provider (Keycloak, Okta, Cognito, your own).
Built-in emailManzano-providedPasswordless 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:

  1. Create an Auth0 Application of type "Regular Web Application."
  2. Set the Allowed Callback URL to your app's callback: https://<your-app>.mzno.me/auth/callback.
  3. Enable the openid, profile, and email scopes under "Connections" or your tenant's default scopes.
  4. Copy the Domain, Client ID, and Client Secret from the application's Settings page.

Per-tenant configuration fields:

FieldValue
issuer_urlhttps://<your-auth0-domain>/ (trailing slash required)
client_idAuth0 application's Client ID
client_secretAuth0 application's Client Secret (stored encrypted; see Credential Store)
scopes["openid", "profile", "email"]
cookie_nameCookie name for the ID token (default mzno_id; change if you run multiple tenants on the same apex domain)
allowed_redirect_pathsList 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:

  1. In the Clerk dashboard, open your application's OAuth settings.
  2. Add your app's callback URL: https://<your-app>.mzno.me/auth/callback.
  3. Use the Frontend API URL as the issuer_url (format https://clerk.<your-domain> or https://<slug>.clerk.accounts.dev for development instances).

Per-tenant configuration fields: same shape as Auth0. Use the Frontend API URL as issuer_url.

WorkOS

What you need from WorkOS:

  1. Configure a WorkOS SSO connection (SAML, OIDC, or one of the pre-built providers).
  2. Use https://api.workos.com/ as the issuer_url.
  3. Supply the WorkOS API Key as client_secret and the Client ID as client_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.

Google

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:

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:

FieldValue
issuer_urlhttps://keycloak.example.com/realms/<realm>
client_idKeycloak client ID
client_secretKeycloak 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:

FieldDefaultMeaning
jwt_issuerhttps://auth.mzno.meIssuer used in the minted JWT
token_ttl_secs600 (10 min)How long a magic link is valid before it expires
cooldown_secs60Minimum interval between magic-link requests for the same email
jwt_expiry_secs86400 (24 h)Lifetime of the signed-in session
email_senderManzano-hostedThe 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 claimHTTP headerctx.principal path
subX-Subctx.principal.id
emailX-Emailctx.principal.claims.email
email_verifiedX-Email-Verifiedctx.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