Files
computer-playbook/docs/contributing/design/iam/oidc.md

5.7 KiB

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.

File Purpose
11_oidc.yml OIDC SPOT, defines OIDC.*
web-app-keycloak OIDC provider role
default.json.j2 Shared client template, MUST stay app-agnostic
scopes Per-app client scopes for extra mappers
realm.json.j2 Realm definition, including the preferred_username mapper
ldap.md LDAP-side counterpart, paired user identifier
common.md Cross-cutting IAM principles and integration checklist