Single sign-on overview: bring your own identity provider
downpipes can let your team sign in with the identity provider you already run. The engine is its own native OpenID Connect relying party, its own native OAuth2 client, and its own native SAML 2.0 service provider, all running as a Worker inside your own Cloudflare account. There is no Cloudflare Access in the path: Access stays additive, never a prerequisite. You point a connection at your provider, the engine completes the federated sign-in itself, and a verified person lands in the console.
This page is for a self-hoster deciding whether and how to wire single sign-on. It sets the no-custody and optional-and-additive expectations, lays out the exact menu of providers, and states the v1 scope limits plainly so nothing surprises you on the step-by-step pages. The procedure itself lives on Connect OIDC or OAuth2 and Connect SAML.
Optional, and never a lockout
Single sign-on is something you add, not something you depend on. The passkey owner front door and the break-glass shared token stay independent of any connection. So an identity provider outage is a greyed-out sign-in button, not a way to be locked out of your own backups: the owner can still reach the console on a passkey, and the break-glass token still works. That independence is the whole reason an external provider is safe to bolt on.
The flip side is just as direct. Disabling or deleting a connection takes effect at once. The engine bumps that connection’s session epoch, which kills the connection’s live sessions, and the connection drops out of the pre-authentication provider list so its sign-in button disappears immediately. There is no lag where a removed provider keeps minting sessions.
However a person signs in, roles work the same way
Cloudflare Access, a native provider here, and a passkey all resolve to the same role and group-to-role machinery. A connection only decides who can authenticate. What they can then do is decided on the Roles and capabilities tab and through group-to-role mapping, identically across every sign-in method.
The provider menu
The preset registry ships 8 named providers plus a Generic OIDC hatch and a Generic OAuth2 hatch. The named presets pre-fill the endpoints, scopes, claim names and the provider-specific gotchas, so you type only a handful of values (a tenant id, an Okta domain, a Keycloak realm) plus your client id and, for a confidential client, a secret.
| Provider | Protocol | Notes |
|---|---|---|
| Microsoft Entra ID | OIDC | Single-tenant v2.0 endpoints; App Roles map cleanly to roles |
| Okta | OIDC | Uses a custom authorisation server so a groups claim can be emitted |
| Google Workspace | OIDC | The hosted-domain (hd) gate is mandatory; signs people in, no groups in the token |
| Keycloak | OIDC | Roles and groups must be toggled into the ID token, not just the access token |
| JumpCloud | OIDC | One shared issuer for all tenants; trust rests on the client id and the connection id folded into each subject |
| Auth0 | OIDC | Issuer carries a mandatory trailing slash; roles are a namespaced custom claim |
| GitLab | OIDC | GitLab.com and self-managed; reads the groups_direct claim |
| GitHub | OAuth2 | No ID token; identity from the GitHub API, keyed on the immutable numeric id |
| Generic OIDC | OIDC | Any spec-compliant .well-known/openid-configuration (PingOne, OneLogin, Authentik, Zitadel, and similar) |
| Generic OAuth2 | OAuth2 | The no-ID-token hatch for any OAuth2 provider (Discord, Bitbucket, and similar) |
The split between the two protocols matters for how trust is established, covered next.
OIDC versus OAuth2 trust
An OIDC connection (every named provider except GitHub) gets a signed id_token from the provider, and the engine cryptographically verifies it. An OAuth2 connection (GitHub, and the Generic OAuth2 hatch) has no id_token at all. For an OAuth2 connection, trust derives from the TLS-protected token exchange, and identity comes from a userinfo call made with the resulting access token. The access token is opaque and is used only as a bearer credential against the provider’s own API.
That is an important honesty point. GitHub and Generic OAuth2 do not verify any token signature, because there is no signed token to verify. They are still safe, because the code exchange happens over a screened HTTPS fetch and the identity is read straight from the provider’s authenticated API, keyed on the immutable user id rather than a renameable username. But do not picture a signed assertion where there is not one.
No custody by construction
A stored connection record never holds a client secret value. The record carries a secret descriptor only: a mode, and an optional non-secret reference. That descriptor is what the console and the audit trail see, so neither ever holds your secret.
There are four named secret-custody modes in the type, but only two are wired in this build. A connection record describes itself with one of these:
| Mode | What the record holds | Wired today |
|---|---|---|
pkce-public |
Nothing. A public client using PKCE only, no secret at all | Yes |
do-plaintext |
A reference only. The secret value lives write-only in a separate engine key, never read back to the console | Yes |
secrets-store |
A Cloudflare Secrets Store binding name | No. Refused at save time |
private-key-jwt |
A private-key reference | No. Refused at save time |
The two unwired modes are the trap to know about, but they cannot trap you silently: a connection configured for secrets-store or private_key_jwt is refused at save time, not at first login. Validation runs before the record is ever written to storage, so a proposal in either mode is rejected with a 400 and nothing is persisted. The console deliberately offers only the two working choices, a public client (PKCE) or a write-only do-plaintext secret, so you cannot even reach that rejected save from the UI.
A SAML connection has no client secret of any kind. Its trust root is the set of public X.509 signing certificates you pin, which are not secret and live in the record openly.
The key fetch cannot be steered off your provider
Because a connection’s issuer is a value you supply, it is also the value an attacker would want to change. The engine screens every call it makes to a provider, discovery, the JWKS key fetch and the token exchange, before the request leaves: the URL must be https, never an IP literal, and never localhost or a .localhost name, with a second classifier for the IPv6 unique-local, link-local and obfuscated forms an IP check alone misses. Redirects are never followed, so a 3xx towards a metadata address or a private range is refused rather than chased. The same pin applies to the Cloudflare Access path, where the key fetch is held to an https host under *.cloudflareaccess.com, so a tampered team-domain value cannot redirect it either. See connect an OIDC provider for the mechanism in full.
Only two secret modes actually sign you in
Use a public client (PKCE) or a write-only do-plaintext secret. Cloudflare Secrets Store and private_key_jwt are not options: the engine refuses to save a connection in either mode, so you would see the rejection immediately, not at first login. The console only surfaces the two that work.
v1 scope limits
A few capabilities are deliberately not in v1. Knowing them up front saves a misconfiguration.
The SAML service provider is sign-only and SP-initiated. It rejects an EncryptedAssertion outright (there is no decryption key, and none is advertised in the metadata), and it does not expose an identity-provider-initiated flow. The connection type carries an allowIdpInitiated flag, but the console hard-codes it off, and the access endpoint enforces SP-initiated-only by consuming a single-use RelayState record that only an SP-initiated request can have created. The full SAML behaviour and the email-trust policy are covered on Connect SAML.
If you have a SAML-only provider and would rather drive it through the OIDC path, a SAML-to-OIDC bridge is an optional self-hosted recipe you stand up yourself. It is not part of the engine.
Who can manage connections
Connecting an identity provider decides who can sign in, which makes it an authentication trust root. A group mapped through a connection can even confer access-admin, so connection management is reserved to the owner-exclusive keys.ceremony capability, not the broader access.policy. A generic administrator cannot add, remove, or enable a provider.
Once a second owner exists on the account, creating, deleting or enabling a connection additionally takes a second owner’s approval (a break-glass admin-token sign-in is the exception). Each of those actions runs through the dual-control owner gate, the same second-owner approval pattern used for the other account-takeover-sensitive operations, so no single owner can quietly change who is allowed in. For the wider second-owner model see dual control for recovery and the change-control page.
Where this fits
Connect OIDC or OAuth2
The end-to-end console flow for a named OIDC provider or a GitHub-class OAuth2 provider, with the redirect URI to register and the pre-save test.
Connect SAML
Stand up native SAML 2.0 sign-in: the signing certificate, the engine-generated SP metadata, the email-trust policy and the exact v1 limits.
Roles and capabilities
What a signed-in person can actually do, decided by the capability map the same way for every sign-in method.
Group-to-role mapping
Turn the groups your provider asserts into downpipes roles, so access follows your directory.
Last updated .