Repository navigation
Replies: 4 comments
|
@xgp - thanks for summarizing this. I think the discussion at KeyConf26 and also discussions at other events show that there is a high interest in atomic import of authentication flows. Let's try to start as simple as possible.
I'd vote for a replace in-place - see below.
When I as an admin make a change to for example enforce a second factor, why should anyone who started an authentication flow be allowed to continue without it without a 2FA? With that security hat on, replacing vs. duplicated flow is no longer a question: If a (significant) change in the logic of the flow is detected, the authentication should restart.
Is there a need to consistently change all flows at the same time? Could this be a replacement by flow to start with? I could still see the point of replacing all flows together, and pruning, but that could be a second step if there is a consistency/business need.
Today you can't change built-in flows in the UI. Allowing that via the API would spin off a longer discussion. Let's postpone any update of built-in flows.
We need to start somewhere. Would it make sense to start with flows without the binding, assuming that this is doing only create or replace?
Sounds like a great addition. Could still be a follow-up. Having a validation when updating a flow (with or without a dry run) sounds like a great improvement.
I've seen myself changing a client in the UI, and then exporting it as JSON to include it in some configuration-as-code. I might want to do the same in the Admin UI. Export and import could also be a manual staging process, or a backup-restore process. |
|
Thanks @ahus1, agreed on all points. We have a PR in the extension (p2-inc/keycloak-atomic-auth-flows#7) so there's something concrete to look at:
For core, we'd propose the smallest version first: replace-by-flow with validation and no bindings. Dry run and export would follow as separate PRs. Does that work? And would you rather see this as a new endpoint under |
|
Good to see we're aligned. I left a comment in the PR. Now I would need to get other maintainers on board / the team that owns the authentication parts. Let's wait for some feedback from them before creating the PR to the main repo. We're pretty busy until end of October, but I hope there will be feedback in November the latest. To advocate for a solution, maybe update the description at the top with the current state of the discussion, plus an example JSON of a flow as it appears on the wire. |
|
Following the contribution guidelines, I opened an issue for this: #54229. I've also updated p2-inc/keycloak-atomic-auth-flows#7 per @ahus1's review. The stored payload hash in the realm attribute is gone. Each flow and config in the payload is now compared with the realm as it is now, and only missing or different ones are written. As a result, a flow edited in the console since the last import is detected and rewritten. A flow that matches keeps its execution ids even when other flows in the payload change. The description above reflects this. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
The problem
Authentication flows are the highest-stakes configuration in a realm: if a flow is wrong, nobody logs in. Both the admin console and the admin REST API change flows incrementally. Adding an execution, setting its requirement, creating a subflow and moving executions into it are all separate requests. Each one commits on its own, against a flow that is already bound and serving logins. Between those commits the realm runs a flow nobody designed: an empty subflow, a
REQUIREDOTP step whose config does not exist yet, or anALTERNATIVEremoved before its replacement was added.Terraform, keycloak-config-cli and scripts make the same incremental calls, only faster. Partial import does not cover authentication flows, and a full realm import is far too blunt for a flow change.
What is missing is a single admin API request that takes a flow tree and its authenticator configs, validates all of it, and applies it in one transaction or not at all.
Proposed first step for core
Keep it as small as possible: create or replace one or more top-level flows, with validation, and no bindings.
AuthenticationFlowRepresentationandAuthenticatorConfigRepresentationfrom realm export JSON. Subflows are referenced byflowAliasand configs by alias. The payload carries no ids. See the example below.400and writes nothing:providerId);Example on the wire
This replaces (or creates) the top-level flow
browser with otp. Its forms subflow requires OTP after the password:{ "authenticationFlows": [ { "alias": "browser with otp", "description": "Cookie, IdP redirect, or username/password followed by OTP", "providerId": "basic-flow", "topLevel": true, "authenticationExecutions": [ { "authenticator": "auth-cookie", "requirement": "ALTERNATIVE", "priority": 10 }, { "authenticator": "identity-provider-redirector", "authenticatorConfig": "redirect to corporate idp", "requirement": "ALTERNATIVE", "priority": 20 }, { "authenticatorFlow": true, "flowAlias": "browser with otp forms", "requirement": "ALTERNATIVE", "priority": 30 } ] }, { "alias": "browser with otp forms", "providerId": "basic-flow", "topLevel": false, "authenticationExecutions": [ { "authenticator": "auth-username-password-form", "requirement": "REQUIRED", "priority": 10 }, { "authenticator": "auth-otp-form", "requirement": "REQUIRED", "priority": 20 } ] } ], "authenticatorConfig": [ { "alias": "redirect to corporate idp", "config": { "defaultProvider": "corporate-oidc" } } ] }This exact payload was posted to Keycloak 26.8 running the extension:
The extension's test suite covers replacing a changed payload in place under the same flow id.
Fields left out (
builtIn,userSetupAllowed,authenticatorFlow: false) take their usual defaults. The extension responds with200, echoes the payload, and addsX-Import-Result: appliedorunchanged. The response shape for core is open for discussion.Follow-ups (separate PRs, not in the first cut)
dryRun=trueruns the whole request, including validation, then rolls it back and returns the same result and errors. Useful as a CI check. Already in the extension.Open questions
/admin/realms/{realm}/authentication/(for example.../authentication/flows/import), orauthenticationFlowsandauthenticatorConfigsupport inpartialImport? Partial import'sOVERWRITEpolicy deletes and recreates, so it would need the replace-in-place behaviour above to keep flow ids stable.204?Reference implementation
We can contribute the PR, with tests ported to the Keycloak test framework and documentation, once the shape is agreed.
Original proposal
Context
At the Keyconf26 group discussion yesterday we presented an extension that Phase Two built with Gusto for applying authentication-flow changes as a single transactional request. Alexander Schwartz suggested a simplified version could be a candidate for Keycloak core, and asked that we open a discussion here before any PR. This is that discussion.
The extension is open source and has been running in Gusto's production environment for about a year:
The problem
Authentication flows are the highest-stakes configuration in a realm: if a flow is wrong, nobody logs in. They are also the configuration Keycloak gives the least protection while changing.
Both the admin console and the admin REST API mutate flows incrementally. Adding an execution is one request, setting its requirement is another, creating a subflow and moving executions into it is several more, and each request commits on its own against a flow that is already bound and serving live logins. Between those commits the realm is running a flow nobody designed: an empty subflow, a
REQUIREDOTP step whose config does not exist yet, anALTERNATIVEremoved before its replacement was added.The usual workarounds do not close that window:
What is missing is a single admin API request that takes the whole set of flows, their authenticator configs, and the bindings that make them live, validates all of it, and applies it in one transaction or not at all.
What the extension does today
It registers a realm admin resource:
The payload carries everything needed for a complete change:
authenticationFlowsAuthenticationFlowRepresentation[], including executions and subflowsauthenticatorConfigAuthenticatorConfigRepresentation[]referenced by alias from the executionsbrowserFlowBindingidpFlowBindings[]clientFlowBindingFlows, configs, executions and every binding are applied inside one Keycloak transaction. Any failure (dangling subflow or config reference, unknown IdP alias, unknown client) rolls the whole import back. The caller needs
manage-realm.One design choice worth explaining up front, since the README does not motivate it well. The server hashes the flows and configs in the payload and prefixes every flow and config alias with that hash. An import never edits a flow that exists; it creates a new, independently bindable set and moves the bindings onto it. Re-posting an identical payload is detected and rejected with
409, andforce=truere-applies the bindings over the already-imported set.That was a Gusto requirement for rollback and compliance: the flow that was serving traffic a moment ago is still in the realm, intact, and rolling back is re-posting the previous payload with
force=true. It also means in-flight authentication sessions keep running against the executions they started on, because nothing they reference is deleted.The cost is the one Alexander raised on Slack: it is not idempotent in the Terraform sense. Each edit adds a new set of flows, the realm accumulates old versions, and somebody has to clean them up. For a core feature that is the wrong default.
What we would propose for core: start simple
A single idempotent request with replace-by-alias semantics and no history:
400and nothing is applied.The versioned-history behaviour could come later as an opt-in (keep the previous N versions, keep forever), if there is interest. It should not be in the first cut.
Discussion points
/admin/realms/{realm}/authentication/...? An extension ofpartialImportwithauthenticationFlowsandauthenticatorConfigsections honouring the existingOVERWRITE/SKIP/FAILpolicy? Something else that better matches admin API conventions?pruneflag could support both.dryRun=truethat runs full validation and returns the errors without writing would make this very useful from CI.What we can contribute
A PR with the endpoint and representation, the validation, tests ported from the extension's Testcontainers suite to the Keycloak test framework, and documentation. Happy to adjust the shape to whatever the maintainers prefer. Feedback on the points above would help us land on the right design before writing code.
All reactions