response_mode=form_post for Apple. The provider-specific quirks are handled — you set two env vars per provider.
Built-in providers
For any other OIDC-compliant IdP, use the generic OIDC adapter — see Generic OIDC.
Set up credentials
Register your app with the provider, then set:<PROVIDER> with GOOGLE, GITHUB, APPLE, etc. (the uppercase form of the table id above).
If a provider’s CLIENT_ID or CLIENT_SECRET is unset, that provider is silently disabled — /api/auth/providers won’t list it.
On Pylon Cloud, set these in Settings → Environment per workspace.
Apple
Apple is special —client_secret is a JWT you sign with your developer ES256 key, not a static string. Pylon mints a fresh JWT on every token exchange so you don’t manage rotation.
response_mode=form_post) instead of the usual GET — pylon’s callback handler accepts both. The user’s identity comes from the id_token JWT in the token response, not a userinfo endpoint.
Note: Apple sends the user’s name only on the first sign-in for a given app. Subsequent logins returnsub+displayName.
Microsoft
SetPYLON_OAUTH_MICROSOFT_TENANT for single-tenant apps:
common (any account — work, school, personal).
Twitter / X
Twitter requires PKCE — pylon generates the verifier/challenge automatically and stores them in the OAuth state record. No extra config needed. Twitter’s userinfo doesn’t include email by default; Pylon synthesizes<username>@x.invalid so account rows still have a value. Apps that require a real email should reject these accounts.
Generic OIDC
For any OpenID Connect provider — Auth0, Okta, Keycloak, Cognito, Logto, Authentik, Zitadel, your homegrown IdP — set:auth0 here). Pylon fetches <issuer>/.well-known/openid-configuration on first use and caches the discovered endpoints. The discovery doc’s token_endpoint_auth_methods_supported decides whether pylon uses Basic auth (the OIDC default) or a client_secret-in-body POST.
How it works
Two ways to invoke
Browser flow (302 redirect)
GET /api/auth/login/<provider>?callback=<url>&redirect=1 returns a 302 directly to the provider. The callback handler sets a cookie and 302s to the callback URL you supplied:
callback (and the optional error_callback) MUST have an origin listed in manifest.auth.trustedOrigins (or PYLON_TRUSTED_ORIGINS). Loopback (http://localhost, 127.0.0.1, [::1] — any port) is always auto-trusted, so pylon dev works without config. See Security plugins → Unified trustedOrigins.
JSON flow (manual)
For SPAs or native apps that want to control the redirect themselves:redirect, the user comes back to your app’s ?code=...&state=xyz URL, and you POST it to the callback:
CSRF protection
Every OAuth start mints a random state token (256 bits, prefixedpylon_) that expires after 10 minutes and is single-use. The callback rejects the request if:
- The state is missing
- The state has expired
- The state was already used (replay)
- The state was minted for a different provider (e.g. Google state on a GitHub callback)
What gets created
On first OAuth sign-in for a given email, Pylon creates aUser row:
(provider, provider_account_id) — NOT by email — so a renamed-email user keeps their account.
Email verification is implicit — the OAuth provider already verified the email, so Pylon stamps emailVerified immediately. Your User entity should have:
Discovering configured providers
Error responses
Provider error messages are sanitized —
client_secret, code_verifier, refresh_token, id_token, and access_token values are redacted before they end up in oauth_error_message redirect URLs or server logs.