Skip to content

SAML with Keycloak

Tested against Keycloak 24+ (new admin console). Goal: login through Keycloak works and your admin user gets org_admin. The keycloak preset reads Keycloak's default mapper output (urn:oid attribute names + Role from the role_list scope) plus a Group list mapper named member — that mapper is the only thing you must add by hand.

1. Create the SAML client

<your realm> → Clients → Create client

  • Client type: SAML.
  • Client ID: https://<domain>/saml/metadata — for SAML clients the client ID is the SP entityID and must match saml.entityID exactly.
  • Next → Valid redirect URIs: https://<domain>/saml/acs → Save.

On the client's Settings tab:

  • Home URL / Master SAML Processing URL: https://<domain>/saml/acs (https, no trailing slash — the SP compares exactly).
  • Name ID format: email.
  • Force POST binding: On (default). If this is off, Keycloak answers over the Redirect binding (GET) and the ACS returns HTTP 405.
  • Sign documents / Sign assertions: On (defaults are fine).
  • Signature keys / "Client signature required": OrbitalReg signs its AuthnRequests, so either
    • Keys tab → import/paste the SP certificate (sp.crt) so Keycloak can verify the request signature, or
    • set Client signature required: Off while bootstrapping and tighten later. With it On and no/imported-wrong cert, the login fails with invalid_signature in the Keycloak server log.

2. Mappers — Group list as member

Keycloak's builtin mappers already emit email (urn:oid:1.2.840.113549.1.9.1), full name (urn:oid:2.16.840.1.113730.3.1.241) and the role list (Role). What's missing out of the box is a groups attribute:

Client → Client scopes → <client-id>-dedicated → Add mapper → By configuration → Group list

  • Name: groups
  • Group attribute name: member ← the preset reads exactly this (the conventional name from the Keycloak docs)
  • Single Group Attribute: On (one multi-valued attribute instead of many single-valued ones)
  • Full group path: Off (emit orbital-admins, not /orbital-admins — the values below match display names)

If the builtin user-property mappers were removed from the dedicated scope, re-add User Property mappers for email and the full name, or the assertion will lack the OID attributes the preset expects.

3. Create the admin group

<realm> → Groups → Create group: orbital-admins; open it → Members → add your user.

Alternatively (or additionally) you can drive roles via Keycloak realm roles: the preset also reads the Role attribute that the default role_list client scope emits, so a realm role named orbital-admins assigned to the user works too.

4. Metadata URL

Keycloak publishes the IdP metadata per realm:

https://<keycloak-host>/realms/<realm>/protocol/saml/descriptor

Use the public Keycloak host — if Keycloak runs behind a different internal hostname, set the frontend URL (Realm settings → General → Frontend URL) so the metadata contains public endpoint URLs; otherwise login redirects will point at unreachable internal addresses ("response code null").

5. Values + deploy

Complete example: charts/orbitalreg/values-keycloak.yaml.example. Essentials:

yaml
saml:
  enabled: true
  idpPreset: "keycloak"
  rootURL: "https://registry.example.com"
  entityID: "https://registry.example.com/saml/metadata"   # = Keycloak client ID
  idpMetadataURL: "https://<keycloak-host>/realms/<realm>/protocol/saml/descriptor"
  existingSecret: "orbitalreg-saml-sp"

auth:
  adminGroups:
    - "orbital-admins"      # group name (no leading slash) or realm-role name
bash
helm upgrade orbitalreg charts/orbitalreg -n <ns> -f values-keycloak.yaml

6. Verify

Log out completely (including the Keycloak SSO session), log back in, then check https://<domain>/auth/whoami: groups must contain orbital-admins (or roles-relevant match via the Role attribute), roles must contain org_admin. Email fallback: add your login email to auth.adminGroups, redeploy, re-login.

Troubleshooting

SymptomCauseFix
Pod crashes, saml init failedDescriptor URL wrong (realm typo) or unreachablecurl the descriptor URL from the pod network
"response code null" on login clickMetadata contains internal Keycloak URLs (no Frontend URL set, metadata fetched internally)Set the realm Frontend URL to the public host; use the public descriptor URL
HTTP 405 after Keycloak loginForce POST binding is off → response arrives as GET, ACS is POST-onlyTurn Force POST binding on
404 on /saml/acsMaster SAML Processing URL / redirect URI has a trailing slash or wrong pathExactly https://<domain>/saml/acs
405 opening /saml/acs in a browserNormal — GET on a POST-only endpointNot an error
Keycloak log: invalid_signature / request rejected"Client signature required" on, but SP cert not imported (or rotated)Import the current sp.crt on the client's Keys tab
Login works but roles: readerGroup list mapper missing, attribute name ≠ member, or Full group path on (/orbital-adminsorbital-admins)Fix the mapper (name member, full path off); or match the full path string in auth.adminGroups
groups empty in whoamiMapper added to the wrong scope, or stale sessionPut the mapper in the client's dedicated scope; full re-login

Reference: what OrbitalReg reads (preset keycloak)

OrbitalReg expectsSAML attribute name
Email (identity)urn:oid:1.2.840.113549.1.9.1
Groups (→ role)member
Display nameurn:oid:2.16.840.1.113730.3.1.241
Role (secondary)Role

See the SSO overview for role mapping and claim-name overrides.

Released under the Apache-2.0 License.