OAuth2
auth-methods hands back a per-provider authURL/state/codeVerifier;
auth-with-oauth2 exchanges the resulting code server-side, fetches
user info, and either signs in the linked record (via _externalAuths),
links onto a same-email match, or creates a new record. A built-in
preset only needs a client id/secret (plus whatever extra fields its own
row below calls out) configured on the collection; any other provider
name is a hand-configured provider using its own authorize/token/userinfo
URLs — or, with nothing but an extra.issuer set, a generic OIDC
provider discovered automatically.
Built-in presets
Section titled “Built-in presets”| Preset name | Scopes (default) | Extra config | Notes |
|---|---|---|---|
google |
userinfo.profile userinfo.email |
— | |
github |
read:user user:email |
— | Falls back to /user/emails when the primary email is private. |
apple |
name email |
teamId, keyId, privateKey |
See Sign in with Apple — no static client secret. |
microsoft |
openid profile email User.Read |
tenant (default common) |
Entra ID (Azure AD) v2.0 endpoint. |
discord |
identify email |
— | |
gitlab |
read_user |
baseUrl (default https://gitlab.com) |
Set baseUrl for a self-hosted instance. |
facebook |
email public_profile |
— | |
twitter |
tweet.read users.read offline.access |
— | X’s OAuth2 has no email scope; PKCE is mandatory (already the default). |
linkedin |
openid profile email |
— | “Sign In with LinkedIn using OpenID Connect”, not the legacy REST API. |
slack |
openid profile email |
— | “Sign in with Slack” (OpenID Connect). |
twitch |
user:read:email |
— | Helix API calls also need the app’s Client ID as a header — handled automatically. |
spotify |
user-read-email user-read-private |
— |
Every preset’s userinfo parsing only ever attributes an email to a
record when the provider itself vouches it’s verified (an
email_verified claim, or GitHub’s own primary && verified email
list) — an unverified address on file with a provider is never enough
to sign into (and silently verify) an existing record that happens to
share it.
Server-driven redirect flow
Section titled “Server-driven redirect flow”With cookie sessions
enabled and settings.meta.appURL configured, the server can drive
the whole provider round trip itself instead of the client handling
authURL/code exchange: GET .../oauth2/{provider}/start?redirect= sends the browser straight to
the provider, and GET .../oauth2/{provider}/callback completes the
login and lands the session in a cookie before redirecting back to
redirect. The URL to register with the provider is always
{appURL}/api/collections/{collection}/oauth2/{provider}/callback.
start’s redirect must be same-origin with appURL or an exact
entry in CORS_ALLOW_ORIGINS; a * origins entry doesn’t count. An
untrusted redirect answers 400 "Untrusted redirect target."
directly — the one failure that doesn’t redirect, since redirecting to
an untrusted target is exactly what that check prevents. Every other
failure redirects to the (now-trusted) redirect with
?cb_error=<code>: cookie_sessions_disabled, app_url_not_configured,
oauth2_disabled, or provider_not_enabled. On the client this is
cb.auth.signIn.social({provider, mode: "redirect"}) (the default in
a browser) or mode: "popup", which opens the same URL in a popup and
resolves once it closes.