Skip to content
downpipes docs

Connect a SAML 2.0 identity provider

The engine is its own native SAML 2.0 service provider, running inside your own Cloudflare account, so a person signs in against your existing SAML provider and lands in the console with no Cloudflare Access in the path. You do all of the downpipes side in the portal: there is no command line to run.

It is written for an owner standing up SAML sign-in. Connection management is reserved to the owner-exclusive keys.ceremony capability. Creating a connection is a high-blast owner action, so with config approval off it takes a second owner’s approval once a second owner exists, unless you are signed in with the bare admin-token break-glass, which is exempt from that second-owner rule and creates immediately (so it can wire the first connection before any second owner exists). If the account has instead opted in to config approval (the dual-control toggle in the Security Centre), that gates every create unconditionally, regardless of owner count: a solo owner’s attempt is queued with no second owner able to approve it, and the break-glass bootstrap token cannot even propose one, since it has no attributable identity. The break-glass token can still disarm the toggle immediately at the Security Centre; an attributable owner’s own attempt to disarm it is, in turn, queued the same way. With config approval off and only one owner, the connection is created immediately on save, since there is no one to approve it. A SAML connection holds no client secret of any kind. Its trust root is the public X.509 signing certificate you pin. If your provider speaks OIDC or OAuth2, use Connect OIDC or OAuth2 instead, and read the SSO overview for the no-custody model and the provider menu.

What v1 supports, and what it does not

Read this first, because SAML has corners and downpipes deliberately occupies a narrow, safe one.

Capability In v1
Signed assertions Yes. The SP refuses an unsigned assertion
SP-initiated sign-in Yes. This is the only flow exposed
Encrypted assertions (EncryptedAssertion) No. Rejected; no decryption key is advertised
Identity-provider-initiated sign-in No. The console hard-codes it off
A SAML connection secret None. The signing certificate is public

The validator carries an allowIdpInitiated flag, but the console always sends it false, and the access endpoint enforces SP-initiated-only regardless: it consumes a single-use RelayState record that only an SP-initiated request could have created, so a replayed or identity-provider-initiated POST is refused. An EncryptedAssertion is rejected with a clear message before any plaintext assertion is even looked for.

Sign-only and SP-initiated

downpipes verifies a signed SAML assertion and starts sign-in from downpipes only. It does not decrypt encrypted assertions, and it does not accept an identity-provider-initiated POST. Configure your provider to sign assertions (or the response) and to initiate from the service provider.

Connect the provider

  1. Open the identity providers screen and pick the SAML 2.0 tile

    In the console, go to Identity providers (the route is /access/idp) and click the “SAML 2.0” tile in the provider logo grid. There is no per-vendor SAML preset; the one generic form drives any SAML 2.0 provider. The form shows an inline “How to add a SAML 2.0 provider” guide and the live ACS URL to register.

  2. Set the connection id, then copy the ACS URL it shows

    Give the connection a display name and a short connection id (1 to 64 characters of lowercase letters, numbers and hyphens, and it must not start or end with a hyphen). The ACS URL is built from the id, so it updates as you type. It is your own console origin plus /admin/saml/acs/<connId>, for example https://console.example.com/admin/saml/acs/saml. This is where your provider POSTs the signed assertion; set your provider’s Assertion Consumer Service URL to exactly this value.

  3. Enter the entity ids and the NameID format

    Set the IdP entity id (your provider’s issuer, exactly as it publishes it), the IdP SSO URL (its HTTP-Redirect sign-in endpoint, which must be https), and the SP entity id (a stable identifier downpipes presents to your provider; it goes in the SP metadata). Choose a NameID format from emailAddress, persistent, or unspecified. The transient format is not accepted, because a per-session pseudonym is not stable enough to key a returning person’s access; it is refused at config time and again when an assertion is consumed.

  4. Paste the IdP signing certificate or certificates

    Paste your provider’s public X.509 signing certificate in PEM form. If your provider rotates certificates, paste several one after another (up to eight) to cover the overlap: the SP pins all of them and accepts a signature under any current one. The SP pins these certificates and ignores the assertion’s own embedded key information entirely, so a forged key in the assertion is never trusted. Pinning the next certificate ahead of a rotation is the calmest way to roll over, and a connection that is already live can be rolled over from its card without signing anyone out (see Rotating the signing certificate).

  5. Choose the email-trust policy

    Pick how an email in the assertion is trusted for auto-binding a pending invite. The recommended default, require-flag, does not trust an email from a connection built in the console at all, because the console has no field for naming the verified-flag attribute it would check, so sign-in resolves on the subject and no invite auto-binds by email. Choose trust-idp instead if you need an invited person’s email to bind on first sign-in; it trusts any well-formed email the assertion carries, shows a warning, and records the choice in the audit log. Either way, the policy only decides auto-binding, never whether the person can sign in. The detail, and the Email attribute you must also set, are in Email binding, the Email attribute, and the trust policy.

  6. Run Test connection

    Select Test connection before you save. The engine runs a read-only pre-save probe over the proposed config (for SAML it checks the metadata and certificate validity). It stores nothing and starts no login. Fix anything that fails before committing.

  7. Save, get the second owner's approval if it applies, then finish at your provider

    Select Add SAML provider. Once a second owner exists, the create runs through the owner gate, so a second owner approves it before it is stored; a solo-owner account creates it immediately instead, since there is no one to approve. If config approval is armed, the gate applies regardless of owner count: a solo owner’s create is queued with no approver, and the break-glass bootstrap token cannot propose one at all; only the break-glass token disarming config approval at the Security Centre unblocks it immediately. Any validation refusal is shown verbatim. After it is created, download the SP metadata from the connection card and register it at your provider, then enable the connection so its sign-in button appears.

Where the three connection values come from

The three identifiers on the form stall people on the first run, because two are read from your provider and one you make up. None of them is a secret.

Field Where it comes from
IdP entity id Your provider’s SAML metadata: the issuer (EntityID) it publishes, copied exactly.
IdP SSO URL Your provider’s SAML metadata: its HTTP-Redirect single sign-on endpoint, an https URL.
SP entity id A stable identifier you choose for downpipes. It is not fetched from anywhere. downpipes presents it to your provider and writes it into the SP metadata, and every assertion must name it as an audience or it is rejected, so set the same value at your provider that you enter here.

If your provider hands you a metadata URL or file, the first two values are inside it. The SP entity id is yours to pick. A stable URL under your own console origin is a good choice, and it does not have to resolve to anything.

Which NameID format to choose

The NameID is the stable identifier downpipes keys a returning person’s access on. The format you pick here is pinned: the assertion’s Subject/NameID Format must match it exactly, or the assertion is rejected, so choose the format your provider actually emits.

Format What it is When to pick it
emailAddress The NameID is an email address. Your provider sends the email address as the NameID, and that address is stable for each person.
persistent An opaque identifier that stays the same for a person across logins and does not travel between services. Your provider can emit a stable per-service pseudonym; this is the safest choice for a stable subject.
unspecified The provider’s own default form, with no particular meaning attached. Your provider emits a Format of unspecified; the value must still be stable per person.

The transient format is never accepted. A transient NameID is a fresh per-login pseudonym, so a returning person would look like a different subject every time. downpipes refuses it when you save the connection and again when it consumes an assertion.

The NameID is only the authorisation subject. It is never used as the person’s email address, even when the format is emailAddress. Trusting and binding an email is a separate step, described next.

Email binding, the Email attribute, and the trust policy

A SAML sign-in always succeeds on the NameID subject. The email is a separate, optional convenience: downpipes uses it only to auto-bind a pending invite to the person on their first sign-in. Two things have to hold for that to happen, and the recommended default deliberately makes it not happen.

The Email attribute must name where the email lives

downpipes reads the email from the assertion attribute you name in the Email attribute field, under Attribute mapping on the form. Leave that field blank and downpipes reads no email at all. It does not fall back to the NameID or to any provider default, so every sign-in is subject-only and no invite auto-binds by email. To bind emails, set the Email attribute to the attribute your provider carries the address in.

The Groups attribute names where groups live

The optional Groups attribute, also under Attribute mapping, names the assertion attribute that carries the person’s group memberships, which group-to-role mapping then turns into downpipes roles. Common values are groups and memberOf, but the right value is whatever your provider is set to send, so read it from your provider’s SAML attribute-mapping configuration rather than guessing. Leave it blank if you grant roles per person rather than by group: sign-in still works, there is simply no group claim to map.

require-flag never trusts an email from a console-built connection

The Email trust picker defaults to require-flag, which trusts an email only when the assertion also carries a verified-email flag attribute. The console has no field for naming that flag attribute, so a connection created in the console has none configured, and under require-flag the email is therefore never trusted. The sign-in still succeeds, but it resolves subject-only and no invite auto-binds. This is safe by design and is the recommended posture.

If you need an invited person’s email to auto-bind on first SAML sign-in, choose trust-idp. It trusts any well-formed email the assertion carries, so set it only when you trust your provider to assert correct addresses. The console shows a warning and records the choice in the audit log. Under either policy, an email that is not trusted is dropped rather than bound to the wrong account, and it is never a sign-in failure.

The SP metadata and the ACS URL

The engine generates the SP EntityDescriptor for you. It is the public document your provider needs, and it carries no secret. The console serves it as XML you download from the connection card.

Field Value
SP metadata content type application/samlmetadata+xml
ACS URL pattern https://<your-console-origin>/admin/saml/acs/<connId>
ACS URL example https://console.example.com/admin/saml/acs/saml
Built from CONSOLE_ORIGIN, so the value you register matches what the SP checks

Use your own console origin in the ACS URL, never a different host. The example uses a custom domain on purpose. The ACS URL the SP declares in the assertion request, and checks the assertion’s Recipient and Destination against, is derived from CONSOLE_ORIGIN rather than the request host, so a forged Host header cannot point the assertion somewhere else.

How the service provider establishes trust

The assertion consumer is fail-closed at every step, in a fixed order. Understanding it explains why a misconfigured provider is rejected rather than half-trusted.

Pinned certificates, never the assertion's own key

The SP verifies the signature against the certificates you pinned on the connection and ignores the assertion’s embedded key information completely. Certificate rollover works because any current pinned certificate that verifies the signature is accepted. It accepts either an assertion-level signature or a response-level (envelope) signature, and in the response-level case the single direct-child assertion it consumes is the one the response signature provably covers.

Anti-wrapping and replay defences

The SP rejects a response that carries more than one assertion, which is the classic XML-signature-wrapping smell. It enforces SP-initiated-only by consuming a single-use RelayState record at the ACS: a missing record (a replay, an identity-provider-initiated POST, or a RelayState this SP never minted) is refused. It also keeps a seen-assertion replay cache keyed on the assertion id, and a browser-binding cookie set at sign-in start guards against a forced-login replay in a foreign browser.

The email-trust policy in detail

SAML has no standard verified-email claim, so the policy decides trust. Under trust-idp a present, well-formed email is treated as verified. Under require-flag the email is trusted only when the configured verified-flag attribute decodes to a truthy value; otherwise the principal is minted subject-only with the email dropped. The engine reads the flag attribute by the name stored on the connection, but the console never sets that name, so a console-built connection has none, and under require-flag its email is always dropped to subject-only regardless of what the provider asserts. Either way the person authenticates on their stable subject. The email is a binding convenience, so an untrusted one is dropped rather than failing the sign-in.

An untrusted email is not a sign-in failure

Under require-flag, an email the assertion does not mark verified yields a subject-only sign-in: the person is signed in on their stable subject, and only the auto-binding of a pending invite is skipped. It never blocks the sign-in and never binds the wrong account.

Rotating the signing certificate

The pinned signing certificate is the SAML trust root, so plan for the day your provider rotates it. The clean way is to cover the rotation ahead of time: paste both the current and the next certificate when you create the connection, up to eight, and the SP accepts a signature under any pinned certificate, so sign-in never breaks while your provider switches over. The pre-save Test connection warns when a pinned certificate is within 30 days of expiry, which is your cue to pin the next one.

A connection that is already live can also be rolled over in place, without ending anyone’s session. Open the connection’s card on Access and identity, Identity providers, expand Roll over the signing certificate, and you get two controls.

Rollover step is a choice of two, and the order matters:

  • Append (overlap, before the cut-over) adds what you paste to the pinned set and keeps every certificate already there. Take this step before your provider switches keys, so an assertion signed by either the old or the new key verifies through the overlap.
  • Replace (prune, after the cut-over) makes what you paste the entire pinned set, so every certificate you do not paste stops being trusted. Take this step only once your provider has finished switching, to retire the old certificate.

Appending first and replacing later is what makes the rollover zero-downtime. Replacing while your provider is still signing with the old key stops sign-in for anyone whose assertion carries the old signature.

IdP signing certificate(s) (PEM) takes the same paste as the create form: one or more public X.509 certificates in PEM, each beginning with -----BEGIN CERTIFICATE-----, pasted one after another for several. The pinned set holds at most eight certificates in total, and on Append that total counts the ones already pinned, so an append that would take the set past eight is refused with nothing stored. The certificates are public; no secret is involved.

Rolling a signing certificate over changes which assertions verify, so it is owner-only, and with four-eyes (dual control) turned on it is queued for a second owner to approve rather than applied. Until they approve, the pinned set has not moved, and the console says so rather than reporting a success.

The old path still works if you prefer it: remove the connection and add it again with the new certificate. That one is not zero-downtime, because removing a connection ends the sessions signed in through it, so those people sign in again. A passkey, the admin token, or another enabled provider still gets an owner back in.

When sign-in fails

Like the OIDC flow, the SAML flow fails generically so it gives an attacker no oracle. The pre-save Test connection is your main diagnostic. Common causes:

Symptom Likely cause Fix
Signature does not verify The pinned certificate is wrong or expired Pin the current public signing certificate; if your provider has rotated, see Rotating the signing certificate
Rejected as encrypted The provider is encrypting the assertion Turn off assertion encryption; v1 is sign-only
Rejected as identity-provider-initiated The provider posted without an SP-initiated request Configure SP-initiated sign-in; start from downpipes
Rejected for the NameID format The provider sends a transient NameID, or a Format other than the one you pinned Pin the exact format your provider emits; transient is never accepted (see Which NameID format to choose)
Assertion sent to the wrong place The ACS URL at the provider does not match Register the exact ACS URL the form shows, including the connection id

The SAML-only tail

If a provider only speaks SAML and you would rather drive it through the OIDC path (for example to share one mapping model), a SAML-to-OIDC bridge is an optional self-hosted recipe you stand up yourself. It is not part of the engine, and the native SAML service provider described here is the supported path for SAML.

Next steps

Last updated .