Configure Keycloak Authentication flow from Ansible #148
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
renovate
waiting on upstream
wontfix
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
CCCHH/ansible-infra#148
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Because the Authentication Flow editor is so horrible, let's try to configure the Browser flow from ansible from a yaml file or similar.
See the image for the flow that implements:
Multiple issues. The attached config now works with (passkey OR (username+password AND (totp OR webauthn authenticator)). Also the time on the VM was wrong.
We really need a way to configure the flow from Ansible.
Claude:
Keycloak: Passkey login + conditional 2FA — flow configuration notes
Goal
Single browser flow that supports:
Keycloak version: 26.4+ (has native Passkeys support).
Final working structure
Realm-level setting: Authentication → Policies → WebAuthn Passwordless Policy → Passkeys toggle enabled.
Gotchas / shortcomings encountered
1. Existing realms are not migrated when Keycloak adds new flow steps
Keycloak's passkey feature (26.4) ships a
Condition - credentialstep baked into the default browser flow for newly created realms, plus native conditional UI in the username/password form. Flows are realm data, so a realm created before 26.4 keeps its old flow structure untouched after upgrade — enabling thePasskeyspolicy toggle alone does not retrofit the skip-2FA logic into an existing flow. It has to be added manually.Source: Passkeys with Keycloak 26.4 or Newer — describes the manual
Condition - Credentialaddition required for exactly this situation.2.
Condition -steps are inert unless the wrapping subflow's requirement is literally "Conditional"Alternative,Required, or a plain subflow will not activate nestedCondition -executions. Easy to get right structurally but leave the parent subflow's own requirement dropdown on the wrong value (we did, twice) — no error is shown, the conditions are just silently ignored and the wrapped step (e.g. OTP Form) always/never runs.Source: Keycloak Server Admin Guide, Configuring authentication → Execution requirements — "Condition executions can only be contained in Conditional subflow."
3.
Condition - Credential"Included" field semantics are non-obvious and unlabeled in the UIIncluded = true→ condition is true when any listed credential type was used.Included = false→ condition is true when none of the listed credential types were used (i.e. inverted/negated).No tooltip conveys this in the admin console; got it wrong once (had it inverted, which caused OTP to be skipped for password logins and required for passkey logins — the opposite of intended).
Source: conditions.adoc, ConditionalCredentialAuthenticatorFactory javadoc.
4. Known bug: mixing OTP Form + WebAuthn Authenticator under one shared
Condition - user configuredbreaks login for users with neither credential configuredDocumented, unresolved as of this writing: when
OTP FormandWebAuthn Authenticatorsit as siblings (Alternative) under a singleCondition - user configuredcheck, users who have configured either work fine, but users with neither get a generic "invalid username or password" error instead of the second factor being skipped, because of anAuthenticationFlowExceptionthrown internally rather than the subflow being cleanly skipped.Workaround used: give each credential type its own
Condition - user configuredcheck inside its own nested Conditional subflow (see "OTP branch" / "WebAuthn branch" above), rather than sharing one condition across two credential types. Each condition then only ever reasons about a single credential type, which avoids whatever internal state the shared-condition case corrupts. Not documented anywhere as an official fix — arrived at by generalizing the pattern that already worked for the OTP-only case.5.
Conditional OTP FormandOTP Formare two different authenticatorsConditional OTP Form(ConditionalOtpFormAuthenticator) is a distinct, self-contained authenticator with its own independent skip/force logic (by user attribute, role, HTTP header, or a configured default), separate from the outer flow'sCondition -executions. If left unconfigured, its internal fallback is to always show the OTP form — it does not silently skip on its own. Easy to mistake for the plainOTP Formexecution when reading a flow tree; worth checking which one is actually present before assuming externalCondition -steps are the only thing controlling it.Source:
ConditionalOtpFormAuthenticator.java.6. Ambiguous which execution actually authenticates a passkey login
With 26.4's native passkey support,
Username Password Formitself can complete a full passkey login (via built-in conditional UI/autofill), which means a separately-addedWebAuthn Passwordless Authenticatorexecution elsewhere in the flow can end up dead/unreached without any indication in the admin console. The only reliable way to confirm which execution handled a given login is to watch theexecution=<id>query parameter in the login-actions redirect during a live test, or enableDEBUGlogging onorg.keycloak.authenticationand read the flow trace.Follow-ups / to verify
WebAuthn Passwordless Authenticatorexecution (if still present anywhere in the flow) is actually reachable, or remove it if native passkey handling viaUsername Password Formmakes it redundant.Condition - user configured.