ansible-infra/roles/keycloak_auth_flow
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Stefan Bethke 32b5f147bc
All checks were successful
/ build (pull_request) Successful in 47s
/ Ansible Lint (push) Successful in 5m31s
/ Ansible Lint (pull_request) Successful in 4m35s
Manage the CCCHH browser authentication flow as YAML
Adds a keycloak_auth_flow role that locally validates flow
definitions (structure, nesting depth, known provider IDs, and a set
of Keycloak authentication-flow gotchas: conditions silently ignored
outside a Conditional subflow, keycloak/keycloak#29515's OTP+WebAuthn
sibling bug, conditional-credential config shape/semantics, and more)
before deploying them idempotently via
middleware_automation.keycloak.keycloak_authentication_v2.

Includes the CCCHH realm's actual "browser passkey and token" flow
(the realm's real browserFlow binding, not the untouched built-in
"browser" flow), exported from the live server so it's now reviewable
and redeployable from this repo instead of only editable in the
Keycloak admin console.
2026-09-02 11:07:45 +02:00
..
defaults Manage the CCCHH browser authentication flow as YAML 2026-09-02 11:07:45 +02:00
library Manage the CCCHH browser authentication flow as YAML 2026-09-02 11:07:45 +02:00
meta Manage the CCCHH browser authentication flow as YAML 2026-09-02 11:07:45 +02:00
tasks Manage the CCCHH browser authentication flow as YAML 2026-09-02 11:07:45 +02:00
README.md Manage the CCCHH browser authentication flow as YAML 2026-09-02 11:07:45 +02:00

keycloak_auth_flow role

Manages Keycloak authentication flows (for example a realm's browser flow) as YAML checked into this repo, instead of hand-editing them in the Keycloak admin console.

Each flow is validated locally (structure, nesting depth, known provider IDs) with the custom keycloak_auth_flow_lint module before being deployed with middleware_automation.keycloak.keycloak_authentication_v2, which applies changes idempotently via a "safe swap" so the realm is never left without a working flow mid-update.

Both tasks run against keycloak_auth_flow__admin_url, which defaults to the local http://127.0.0.1:8080 — i.e. this role is meant to run on the Keycloak host itself, talking to the Keycloak container directly, since the public admin hostname is IP-restricted.

Required Arguments

  • keycloak_auth_flow__admin_password: Password of keycloak_auth_flow__admin_username (an admin in the master realm).

Optional Arguments

  • keycloak_auth_flow__flows: List of flow definitions to deploy. Defaults to []. Each item:
    • realm: Realm the flow belongs to.
    • alias: Name of the flow.
    • providerId: basic-flow (default) or client-flow.
    • description: Optional human-readable description.
    • authenticationExecutions: Nested list describing the flow's steps, in the shape documented by middleware_automation.keycloak.keycloak_authentication_v2 — each item is either an execution (providerId, requirement, optionally authenticationConfig) or a sub-flow (subFlow, requirement, optionally subFlowType, and a nested authenticationExecutions). Up to 10 levels of sub-flow nesting are supported.
  • keycloak_auth_flow__admin_url: Defaults to http://127.0.0.1:8080.
  • keycloak_auth_flow__admin_username: Defaults to admin.

Validation checks (keycloak_auth_flow_lint)

The lint module runs entirely offline (no Keycloak credentials or network access needed) and checks each flow definition before it's ever sent to the server.

Errors (fail the run)

  • Node shape: every entry in authenticationExecutions must be a mapping with no unknown keys, exactly one of providerId (an execution) or subFlow (a sub-flow), and a requirement that is one of REQUIRED/ALTERNATIVE/DISABLED/CONDITIONAL. A sub-flow's subFlowType (when given) must be basic-flow or form-flow, its name must be a non-empty string unique within the flow, and it must define at least one child execution. These mirror the shape middleware_automation.keycloak.keycloak_authentication_v2 itself expects — see its module source.
  • Nesting depth: at most 10 levels of sub-flow nesting, matching the limit built into keycloak_authentication_v2 (see the module source linked above; the depth was raised from 4 to 10 in ansible-middleware/keycloak#376).
  • authenticationConfig shape: when present, must be a mapping with only alias (non-empty string) and config (a mapping) — again mirrors what keycloak_authentication_v2 accepts.
  • Condition execution outside a CONDITIONAL sub-flow: a conditional-* execution is silently ignored — no error, the condition just never fires — unless its immediate parent sub-flow's own requirement is literally CONDITIONAL. See the Keycloak Server Admin Guide, "Configuring authentication → Execution requirements": "Condition executions can only be contained in Conditional subflow."
  • OTP + WebAuthn siblings under one shared conditional-user-configured: this hits keycloak/keycloak#29515 — users with neither credential configured get a generic login error instead of the check being skipped (also discussed in keycloak/keycloak#14988). The fix is to give each credential type its own nested CONDITIONAL sub-flow with its own conditional-user-configured check.
  • conditional-credential config: requires an authenticationConfig with a non-empty credentials entry, and included (if set) must be the string "true" or "false" — Keycloak stores authenticator config values as strings. See ConditionalCredentialAuthenticatorFactory javadoc and conditions.adoc.

Warnings (non-fatal)

  • Unrecognized providerId: not in the module's built-in list of known Keycloak provider IDs — could be a typo, or a legitimate custom SPI provider. See the constant KNOWN_PROVIDER_IDS in the module source for how that list was compiled and how to check or extend it (live server query vs. Keycloak's authenticator factory sources).
  • CONDITIONAL requirement on a non-conditional-* provider: usually a sign the wrong execution ended up under a Conditional sub-flow.
  • conditional-credential included semantics reminder: restates what the configured included value actually means (true → condition is true when any listed credential was used; false → true when none were used). This is unlabeled in the admin console and easy to get backwards — see the same conditions.adoc reference as above.
  • auth-conditional-otp-form with no config: unlike the plain auth-otp-form, Conditional OTP Form's skip/force logic is entirely self-contained (by user attribute, role, header, or default) and does not inherit a wrapping Condition-* check; with no config it defaults to always showing the OTP form. See ConditionalOtpFormAuthenticator.java.
  • Ambiguous passkey execution: when both auth-username-password-form and webauthn-authenticator-passwordless appear anywhere in the flow. With native passkey support, the username/password form can itself complete a full passkey login, so it can be unclear — and unverifiable from the admin console — which execution actually authenticated a given login. No online source for this one; verify via the execution=<id> query parameter during a live test, or DEBUG logging on org.keycloak.authentication.

Example

keycloak_auth_flow__flows:
  - realm: ccchh
    alias: browser passkey and token
    providerId: basic-flow
    authenticationExecutions:
      - providerId: auth-cookie
        requirement: ALTERNATIVE
      - subFlow: forms
        requirement: ALTERNATIVE
        authenticationExecutions:
          - providerId: auth-username-password-form
            requirement: REQUIRED