Skip to main content
Pylon ships native OAuth for 25 providers out of the box, plus OpenID Connect discovery for any compliant IdP (Auth0, Okta, Keycloak, Cognito, Logto, Authentik, Zitadel…). Users click sign in, get redirected to the provider, come back with a session token. CSRF-protected via single-use state tokens that survive a server restart mid-handshake. PKCE for providers that require it (Twitter/X, Kick). 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:
Replace <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.
Apple POSTs the callback (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 return sub + email only. Pylon stores what it can and doesn’t overwrite an existing displayName.

Microsoft

Set PYLON_OAUTH_MICROSOFT_TENANT for single-tenant apps:
Default is 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:
The provider id is the lowercase of the env-var prefix (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:
You navigate the browser to redirect, the user comes back to your app’s ?code=...&state=xyz URL, and you POST it to the callback:
Response:

CSRF protection

Every OAuth start mints a random state token (256 bits, prefixed pylon_) 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)
State storage defaults to in-memory; the runtime swaps in a SQLite or Postgres backend so a server restart mid-handshake doesn’t break in-flight sign-ins. PKCE verifiers ride along in the same record for providers that need them.

What gets created

On first OAuth sign-in for a given email, Pylon creates a User row:
On subsequent sign-ins, Pylon looks up the user by (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

Use this in your sign-in UI to render only the buttons you’ve configured.

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.