SAML 2.0 single sign-on
Set up SAML SSO with AD FS, Authentik or Keycloak.
On this page
PowerDNS-AuthAdmin can authenticate users against any SAML 2.0 identity provider - AD FS, Authentik SAML, Keycloak SAML, Okta SAML, Azure AD SAML, PingFederate, Shibboleth. Signed assertions are required by default; encrypted assertions are optional. Group → role mapping mirrors the OIDC equivalent.
Architecture is captured in ADR-0021. This page is the operator setup guide.
Quick reference#
| Endpoint | Purpose |
|---|---|
GET /api/auth/saml/<slug>/login | SP-initiated sign-in (redirects to IdP). |
POST /api/auth/saml/<slug>/acs | Assertion Consumer Service (callback). |
GET /api/auth/saml/<slug>/metadata | SP metadata XML for IdP registration. |
GET /api/auth/saml/<slug>/slo | Single-logout (SP-initiated). |
The <slug> is the URL-safe identifier you choose when creating the provider.
It cannot be changed once the provider is created (changing it would break
in-flight sign-in attempts).
Setting up a SAML provider#
1. Generate the SP signing keypair#
The Service Provider (this app) signs every AuthnRequest with its private key so the IdP can confirm the request came from the registered SP. Generate a fresh keypair before adding the provider:
openssl req -x509 -newkey rsa:2048 \
-keyout sp.key -out sp.crt \
-nodes -days 1825 \
-subj "/CN=<slug>"The private key is encrypted at rest by AES-256-GCM via
APP_ENCRYPTION_KEY; the public certificate is published in SP metadata.
2. Add the provider in the admin UI#
- Navigate to Admin → Authentication → Add provider and pick SAML 2.0.
- Fill in:
- Display name - shown on the login button.
- Slug - URL-safe identifier (lowercase, dashes; e.g.
corp-sso). - IdP entityID - the IdP's Issuer URI.
- IdP SSO URL - the IdP's SAML 2.0 sign-in endpoint (HTTP-Redirect binding).
- IdP SLO URL (optional) - Single Logout endpoint.
- IdP signing certificate - paste the public PEM cert the IdP uses to sign Responses.
- SP private key + cert - paste the PEMs you generated above.
- Save. Copy the SP metadata URL from the provider detail page -
/api/auth/saml/<slug>/metadata- and register it in your IdP.
3. Register the SP with the IdP#
Most IdPs accept SP metadata via URL or uploaded XML file. Worked examples:
Authentik SAML#
- Providers → Create → SAML Provider.
- Authorization flow:
default-provider-authorization-implicit-consent. - ACS URL:
https://<app-url>/api/auth/saml/<slug>/acs. - Audience / Issuer:
https://<app-url>/api/auth/saml/<slug>/metadata(this is the SP entityID PowerDNS-AuthAdmin derives from APP_URL + slug). - Signing key/certificate: select the keypair Authentik should sign with. Then go back to PowerDNS-AuthAdmin and paste that public cert into IdP signing certificate.
- Property mappings - at minimum: an
emailmapping for the user's email. Optional:groupsmapping for group → role assignments. - Create an Application bound to this Provider and assign the relevant users / groups.
Keycloak SAML#
- Clients → Create client → SAML.
- Client ID:
https://<app-url>/api/auth/saml/<slug>/metadata. - Root URL:
https://<app-url>. - Valid Redirect URIs:
https://<app-url>/*. - Master SAML Processing URL / ACS:
https://<app-url>/api/auth/saml/<slug>/acs. - Signing keys → Client signing key: import the SP public cert
(
sp.crt). - Client scopes → Mappers - add at minimum:
- User Property
email→ SAML Attributeemail. - User Property
firstName→ SAML Attributename(or compose). - (Optional) Group List → SAML Attribute
groups.
- User Property
Then in PowerDNS-AuthAdmin paste Keycloak's IdP signing certificate (under Realm Settings → Keys) into IdP signing certificate.
AD FS#
- AD FS Management → Relying Party Trusts → Add Relying Party Trust.
- Import data about the relying party from a file - point at the SP
metadata XML (download from
/api/auth/saml/<slug>/metadata). - Access Control Policy: Permit everyone (or scope as required).
- Edit Claim Issuance Policy → Add Rule → Send LDAP Attributes as Claims:
- LDAP attribute:
E-Mail-Addresses→ Outgoing claim:email. - LDAP attribute:
Display-Name→ Outgoing claim:name. - (Optional) Token-Groups - Unqualified Names → Outgoing claim:
groups.
- LDAP attribute:
- Copy the AD FS Token-signing certificate from Service → Certificates and paste it into PowerDNS-AuthAdmin's IdP signing certificate field.
Group → role mapping#
Same shape as OIDC. On every successful sign-in, the user's group attribute
(default groups) is matched against the provider's mappings; each match
materialises a role_assignments row tagged with provider_id = this
provider's id. Removed groups → revoked assignments. Admin-issued
assignments are never touched.
Encrypted assertions (optional)#
If you want the IdP to encrypt assertions to this SP:
- Generate a second keypair the same way and save it as
sp-enc.key/sp-enc.crt. - In the provider form, tick Configure assertion encryption keypair and paste the new PEMs.
- Update the IdP's SP registration with the new encryption cert (most IdPs advertise the encryption cert separately from the signing cert).
- Tick Require encrypted Assertion if you want to refuse plaintext assertions.
Provisioning (provisioning.yaml)#
saml:
- slug: corp-sso
name: Corporate SSO
idp_entity_id: https://idp.example.com/saml
idp_sso_url: https://idp.example.com/saml/sso
idp_slo_url: https://idp.example.com/saml/slo
idp_signing_cert: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
sp_signing_key: |
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
sp_signing_cert: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
claim_email: email
claim_name: name
claim_groups: groups
group_mappings:
- group: DomainAdmins
role: super-admin
scope: global
- group: NetOps
role: operator
scope: globalSee provisioning.example.yaml for the full
schema.
Troubleshooting#
saml-state-missing- thepda_saml_statecookie wasn't set when the ACS handler ran. Usually means the user's session expired between /login and the IdP round-trip (10-minute TTL), they switched browsers, or their browser blocked third-party cookies on the IdP origin.saml-exchange-failed- the assertion failed verification. Check the app logs for the specificsaml.acs.verify-failedline; common causes:- InResponseTo mismatch - the IdP replied to a different RequestID (sometimes happens when the IdP holds onto a stale session).
- Signature verification failed - the IdP's signing cert changed and you haven't updated the IdP signing certificate field.
wantAuthnResponseSignedfailed - the IdP signs only the inner assertion; turn off Require signed Response on the provider.
saml-not-authorized- the email domain wasn't in the allow-list. Check the provider's Allowed email domains field (allowed_email_domainsin provisioning YAML).