Keycloak SAML

Use Keycloak as SAML identity provider for Kimai

Go back to general SAML configuration for Kimai.

SAML authentication with Keycloak accounts has proven to work with the following configurations.

Screenshots might be outdated, please check the text version below each image.

Important settings

  • singleSignOnService.url: https://{keycloak-domain}/realms/{realm}/protocol/saml
  • In Kimai: kimai.saml.connection.idp.x509cert = value from IdP ds:X509Certificate
  • In Kimai: kimai.saml.connection.security.authnRequestsSigned: true
  • In Keycloak: enable Force name ID format

Add a client for Kimai SAML

Add a client

Configure the client

Deactivate *Client Signature Required* (1)
Deactivate *Client Signature Required* (2)

Certificates

  • IdP certificate: Obtain the ds:X509Certificate from Keycloak’s SAML descriptor:
    https://{keycloak-domain}/realms/{realm}/protocol/saml/descriptor
    ⚠️ Do not use the RS256 realm key from Realm Settings.

  • SP keys: In Kimai config, set:

    • sp.privateKey → private key generated in Keycloak (Clients → Kimai → Keys)
    • sp.x509cert → corresponding public key

Create user attributes

Keycloak client mappers (inside the {client}-dedicated scope)

Remove the default role list, then add:

  • X500 emailSAML Attribute Name: Email, SAML Attribute NameFormat: Unspecified
  • X500 surnameSAML Attribute Name: LastName, SAML Attribute NameFormat: Unspecified
  • X500 givenNameSAML Attribute Name: FirstName, SAML Attribute NameFormat: Unspecified
  • Role listRole attribute name: Roles, SAML Attribute NameFormat: Unspecified, Single Role Attribute: On
Kimai user attributes (listing)
Kimai user attributes (X500 GivenName)
Kimai user attributes (X500 Surname)
Kimai user attributes (X500 Email)

Client Scopes / Roles

Make sure to map unique Keycloak roles to Kimai roles.
If you reuse the same Keycloak role, the last mapping wins.

  • Go to Configuration -> Client Scopes -> role_list
  • Select Tab “Mappers”, edit “role_list”
  • Set” Single Role Attribute” to “ON”

Example:

{ saml: Admins, kimai: ROLE_SUPER_ADMIN }
{ saml: Management, kimai: ROLE_ADMIN }
{ saml: Teamlead, kimai: ROLE_TEAMLEAD }

Configure local.yaml

And here is the matching Kimai configuration:

kimai:
    saml:
        provider: keycloak
        activate: true
        title: Keycloak
        mapping:
            - { saml: $Email, kimai: email }
            - { saml: $FirstName $LastName, kimai: alias }
        roles:
            resetOnLogin: true
            attribute: Roles
            mapping:
                - { saml: Admins, kimai: ROLE_SUPER_ADMIN }
                - { saml: Management, kimai: ROLE_ADMIN }
                - { saml: Teamlead, kimai: ROLE_TEAMLEAD }
        connection:
            # You SAML provider, here an example for Keycloak
            idp:
                entityId: 'https://example.com/realms/master'
                singleSignOnService:
                    url: 'https://example.com/realms/master/protocol/saml'
                    binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
                x509cert: 'cert from https://{keycloak-domain}/realms/{realm}/protocol/saml/descriptor > ds:X509Certificate'
           # Your Kimai instance, replace https://127.0.0.1:8010 with your base URL
            sp:
                entityId: 'Kimai2'
                assertionConsumerService:
                    url: 'https://example.com/auth/saml/acs'
                    binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
                singleLogoutService:
                    url: 'https://example.com/auth/saml/logout'
                    binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
                privateKey: 'private key generated in the Keycloak > Clients > Kimai > Keys'
                x509cert: 'public key generated in the Keycloak > Clients > Kimai > Keys'
            # only set baseurl, if auto-detection doesn't work
            baseurl: 'https://example.com/auth/saml/'
            strict: true
            debug: true
            security:
                nameIdEncrypted: false
                authnRequestsSigned: true
                logoutRequestSigned: false
                logoutResponseSigned: false
                wantMessagesSigned: false
                wantAssertionsSigned: false
                wantNameIdEncrypted: false
                requestedAuthnContext: true
                signMetadata: false
                wantXMLValidation: true
                signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
                digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256'
            contactPerson:
                technical:
                    givenName: 'Kimai Admin'
                    emailAddress: 'kimai-tech@example.com'
                support:
                    givenName: 'Kimai Support'
                    emailAddress: 'kimai-support@example.com'
            organization:
                en:
                    name: 'Kimai'
                    displayname: 'Kimai'
                    url: 'https://www.kimai.org'

You should now be able to test the Login by visiting https://timetracking.example.com/ and clicking on the Keycloak title of the SAML method, you defined earlier.

Top