OIDC πŸͺͺΒΆ

This page describes how application roles consume the platform’s OIDC provider, Keycloak. The variable tree lives in 11_oidc.yml under defaults_oidc. For the cross-cutting IAM principles, including the paired identifier rule shared with LDAP, see common.md. For LDAP-side details see ldap.md.

Variable Tree 🌳¢

The OIDC tree is a lazy group_var: it resolves in the consuming role’s context, so the issuer family-aligns to that consumer (clearnet page β†’ clearnet issuer, onion page β†’ onion issuer). Inventory overrides therefore live under OIDC_OVERRIDE: β€” an inventory-level OIDC: block would shadow the lazy resolution and is rejected by a constructor assert.

The keys consumers care about:

Key

Meaning

OIDC.URL

Base URL of the Keycloak instance

OIDC.CLIENT.ID

Client identifier, defaults to SOFTWARE_DOMAIN

OIDC.CLIENT.SECRET

Client secret, MUST come from the inventory (OIDC_OVERRIDE.CLIENT.SECRET)

OIDC.CLIENT.REALM

Realm the client is registered in

OIDC.CLIENT.ISSUER_URL

Issuer URL, equal to discovery issuer

OIDC.CLIENT.DISCOVERY_DOCUMENT

.well-known/openid-configuration URL

OIDC.CLIENT.AUTHORIZE_URL

Authorization endpoint

OIDC.CLIENT.TOKEN_URL

Token endpoint

OIDC.CLIENT.USER_INFO_URL

Userinfo endpoint

OIDC.CLIENT.LOGOUT_URL

RP-initiated logout endpoint

OIDC.CLIENT.CERTS

JWKS endpoint, equal to discovery jwks_uri

OIDC.CLIENT.ACCOUNT.URL

User self-service console root

OIDC.CLIENT.ACCOUNT.PROFILE_URL

Personal-info section deep link

OIDC.CLIENT.ACCOUNT.SECURITY_URL

Login and security section deep link

OIDC.CLIENT.CHANGE_CREDENTIALS

Direct link to the credentials editor

OIDC.CLIENT.RESET_CREDENTIALS

Public password-reset entry point

OIDC.BUTTON_TEXT

Default label for SSO login buttons

When a role needs an OIDC endpoint, it MUST template the corresponding OIDC.CLIENT.* value rather than build the URL from OIDC.URL itself. The URL builders in 11_oidc.yml already handle realm and protocol concerns.

Claim Attributes 🏷️¢

OIDC.ATTRIBUTES lists the claim names that downstream applications read out of the userinfo response and ID token:

Key

Default value

Meaning

OIDC.ATTRIBUTES.USERNAME

preferred_username

Stable user identifier

OIDC.ATTRIBUTES.GIVEN_NAME

givenName

First name

OIDC.ATTRIBUTES.FAMILY_NAME

surname

Last name

OIDC.ATTRIBUTES.EMAIL

email

Email address

Rules:

  • Application roles MUST identify users via OIDC.ATTRIBUTES.USERNAME. They MUST NOT introduce a private claim name (for example user_id, uid, or oauth_uid) when preferred_username already satisfies the requirement.

  • Roles MUST NOT hardcode the literal value preferred_username. Always template {{ OIDC.ATTRIBUTES.USERNAME }}.

  • The standard OIDC sub claim is permitted as an alternative when the application explicitly requires a UUID-shaped identifier and stores its own mapping table. In that case the role MUST document the deviation in its README.

  • The OIDC username claim is paired with the LDAP login attribute by design. See ldap.md for the LDAP-side rule.

Keycloak Client Templates πŸ“¦ΒΆ

Keycloak client definitions are imported from clients. The default template in default.json.j2 is shared by every application client.

Rules:

  • The shared default.json.j2 MUST stay generic. App-specific protocol mappers, claims, or scopes MUST NOT be added there because the change affects every other client.

  • An app that needs an extra mapper MUST add it through a per-client scope. See nextcloud.json.j2 for the precedent: a dedicated scope holds the Nextcloud-specific UID and quota mappers and is attached only to the Nextcloud client.

  • New scope files MUST be referenced from the consuming app via its KEYCLOAK_RBAC_GROUP_CLAIM style configuration or via the optionalClientScopes list in the client template.

Consumer Pattern 🧩¢

A typical OIDC-consuming role wires the values into its environment template, for example env.j2:

OIDC_ISSUER={{ OIDC.CLIENT.ISSUER_URL }}
OIDC_DISCOVERY=true
OIDC_SCOPE=openid,profile,email
OIDC_UID_FIELD={{ OIDC.ATTRIBUTES.USERNAME }}
OIDC_CLIENT_ID={{ OIDC.CLIENT.ID }}
OIDC_CLIENT_SECRET={{ OIDC.CLIENT.SECRET }}

Every application role SHOULD follow the same shape: discovery on, the OIDC.CLIENT.* endpoints templated, the user identifier read from OIDC.ATTRIBUTES.USERNAME, and the scopes limited to what the app actually consumes.