Keyway SSO for Craft CMS › Guides

Craft CMS SAML SSO: signing in to the control panel with SAML 2.0

Craft CMS has no built-in SAML sign-in for the control panel, so it takes a plugin. This page is the provider-independent half of the job: what the three values are, in which order to fill them in so the save actually goes through, and what the plugin checks before it lets anybody in.

The three values, and where they come from

ValueWhat it isWhere it comes from
ACS URLThe assertion consumer service: the address your provider POSTs the signed assertion to.This site. Copy it from ACS URL this site answers on on the settings screen — it is <BASE>/actions/keyway-sso/sso/acs.
SP entity IDAn identifier for this site, not an address. Any stable string works as long as both sides carry exactly the same one.You choose it. The usual choice is this site's base URL without a trailing slash, offered as a copy field above the SP entity ID box.
MetadataAn XML document describing this service provider, which some consoles can import instead of retyping.This site, at <BASE>/actions/keyway-sso/sso/metadata and behind the Download metadata button — but only after a complete save, see below.

From the provider you need three things in return: its IdP entity ID (the issuer it stamps on assertions), its IdP SSO URL (where the browser is sent to log in) and its IdP signing certificate. The certificate has to be the full X.509 certificate, PEM or bare base64 — a fingerprint is rejected, because a fingerprint proves which certificate the message carried, not which one you trust.

The order that actually works

The settings screen is validated as a whole. With Protocol set to SAML 2.0, a save is rejected until IdP entity ID, IdP signing certificate, IdP SSO URL, SP entity ID and ACS URL are all present and well-formed — and a rejected save stores nothing. So:

  1. Open Settings → Plugins → Keyway SSO, set Protocol to SAML 2.0, and copy the ACS URL and the site URL. Both are readable before you save anything, because the plugin builds them from its own registered routes.
  2. Create the application at your provider and give it those two values.
  3. Come back with the provider's three values and save the complete configuration.
  4. Only now, if your console wants it, hand over Download metadata or the Metadata URL — and re-check the application afterwards.
  5. Map attributes and groups, then test in a private window and read the diagnostics row.

Before the first complete save, the metadata address answers 404 and the download button is inert. That is deliberate, not a fault: a metadata document naming a blank entity ID would be accepted by the provider and would configure it against a sign-in that can never validate. The metadata file is a way to hand over a finished configuration, not a way to start one.

Note what the document does not carry, so nothing surprises you: no KeyDescriptor (the plugin has no field for an SP certificate), a NameIDFormat of unspecified, AuthnRequestsSigned="false", and no SingleLogoutService until logout is configured.

What the signature has to cover

Two rules that bite later, not on day one

The Name ID has to be stable. Accounts the plugin creates are recognised on later logins by account, issuer and subject — the NameID. Use persistent or emailAddress. transient is a fresh value on every login by definition, so every second login of every user is refused with linking_disabled. The plugin never reads the format attribute, so this is a configuration rule, not a check you can fail, and there is no error message pointing at it.

Logins must start on the Craft side. The plugin requires its own RelayState and a matching InResponseTo, so an assertion belonging to no login of ours is refused as unsolicited_response. In practice: the dashboard tile for this application will not sign anybody in. Send people to the Craft login screen and let them press the SSO button.

Single Logout: SAML only, one way, opt-in — and not with Okta

Single Logout (SAML only, one direction, opt-in: it needs your provider's logout URL and an SP private key before it does anything): when your identity provider sends a signed SAML LogoutRequest, the plugin ends the matching Craft session. Signing out of Craft does not end the session at your identity provider, and Okta will not send such a request after an ordinary Okta sign-out — so with Okta as your only provider, plan on single sign-on, not single logout. Verified end to end against Keycloak 26.0; OpenID Connect has no logout support.

Mechanically, on a SAML installation that opts in: the endpoint is /actions/keyway-sso/sso/slo, HTTP-Redirect binding only, and it stays shut until IdP single logout URL and an SP private key are both set — until then it refuses every message and the metadata document deliberately advertises no SingleLogoutService, because advertising one would make every provider that reads the document send logout requests to an address that answers nothing. A request is honoured only when its subject matches the session in the browser that carried it; a mismatch ends nothing and is answered UnknownPrincipal, which is also what a browser with no session gets.

What to check when it fails

Open Settings → Plugins → Keyway SSO and read the warnings at the top, then press Open sign-in diagnostics. Every attempt is a row with its stage, outcome, a machine reason code and a Details panel holding the attributes that arrived, what they mapped to and the decision taken. The person signing in only ever sees one vague sentence — that is deliberate, so that the login endpoint cannot be used to tell an existing account from a missing one.

Reason codeWhat it means
confirmation_invalid, destination_mismatchThe assertion's Recipient or the response's Destination is not literally equal to the configured ACS URL. Put the three addresses side by side: the one registered at the provider, the one in the ACS URL field, and the read-only ACS URL this site answers on. A trailing slash is enough.
audience_mismatchThe AudienceRestriction does not name this SP entity ID.
issuer_mismatchThe assertion issuer is not the configured IdP entity ID, compared byte for byte.
signature_invalidThe wrong certificate in IdP signing certificate, or the provider rotated its key.
assertion_expiredA time window did not check out. Compare the clocks on both machines before touching anything else.
attributes_rejectedA required attribute was not sent, or a value mapped to email is not a valid address. Details → Attributes received is the ground truth about what your provider sent.
no_group_match, jit_disabled, linking_disabledThe login was understood and then refused by your own policy. The fix is on the Craft settings screen, not at the provider.
binding_not_issuedA notice, not a refusal: the login ran without the cookie tying it to the browser that started it, because the callback is not served over HTTPS.

The full table, including the codes that appear only on unusual installations, is on when single sign-on does not sign anybody in. Provider specifics: Okta, Keycloak.

Keyway SSO for Craft CMS 5

SAML 2.0 and OpenID Connect sign-in for the Craft control panel: attribute and group mapping, just-in-time accounts, an admin password fallback with a guard that switches admin password login back on, with a warning, if your settings would otherwise leave nobody able to sign in, and a diagnostics screen that tells you why a login was refused. The plugin is in final testing and is on its way to the Craft Plugin Store.

Read the deployment documentation