Repository navigation
Replies: 1 comment
|
My understanding and experience is that it is in fact possible to roll out refresh-token-only DPoP for public clients selectively using the client-attributes condition. That said, I agree with the outlined lack of symmetry between rolling out full DPoP and enabling DPoP-bound refresh tokens only, when starting adoption incrementally. Using a policy (condition + executor) and introducing a custom client attribute to achieve such an incremental rollout with refresh tokens only, feels like an indirect way of realizing what should perhaps have been a client setting. I have read the discussion in #26277 and understand the argument for not adding unnecessary extra settings while there already are many client settings, but at the same time settings are added elsewhere (eg #51798) where this is not debated. It makes me wonder what the principle standpoint is on the purpose of policies. Shouldn't policies be used for restrictions that further narrow down the configuration based on run-time conditions? In addition to this, it was confusing to me at first that the executor can both enforce the "DPoP required" client setting and enforce DPoP for clients that do not enable that setting. It seems like the current implementation was created for a particular case where DPoP needed to be required for public clients and only refresh tokens should be DPoP-bound, but having a single setting for this makes it less generally useful. That said, and I guess in line with what is suggested above, it would feel more natural to me to have two client settings:
The dpop-bind-enforcer executor could still be used to further narrow down the scope of DPoP or automatically require it on newly created clients (using auto-configure), but would not be needed anymore to only bind refresh tokens. It'd be interesting to hear the maintainers views on this. @mposolda, perhaps you could share your view since I noticed you had been involved in the earlier discussion on this topic as well? |
Uh oh!
There was an error while loading. Please reload this page.
Summary / current state
Keycloak 26.4 added the ability to bind DPoP to the refresh token only — see #26277, PR #42783. This is a great fit for gradual DPoP adoption: a public client can harden its long-lived refresh credential without every resource server having to understand DPoP-bound access tokens.
However, there is a discrepancy to how a user can roll full DPoP vs refresh token only DPoP
Full DPoP Binding (Access and Refresh tokens)
A durable per-client setting ("Require DPoP bound tokens" /
dpop.bound.access.tokens), enforced in core token issuance and easily targetable to a single client.DPoP binding to Refresh Token only
This mode can only be turned on via the
dpop-bind-enforcerclient-policy executor (allow-only-refresh-token-binding), which enables for all public clients.I'd like to discuss closing that asymmetry: is there appetite for a per-client way to enable refresh-token-only binding, symmetric with the existing per-client full-DPoP setting?
The problem: there's no easy way to adopt this incrementally
In a real realm the current "policy executor" model makes it hard to rollout:
Enabling the executor realm-wide (i.e.
any-client) therefore isn't a workable substitute for per-client: it would require every public frontend — web and mobile, across teams, release cycles and app stores — to ship DPoP support simultaneously, plus clients you don't control. Realistically adoption has to be incremental, which is exactly what isn't currently expressible.Scoping the executor to a single client (or group thereof) via a condition (e.g.
client-attributes) doesn't achieve this either, because those client-selecting conditions abstain at the token-endpoint events where refresh-only binding is enforced (related: #45740). So the enforcement is realm-wide by nature.Motivation - my own attempt to roll out incrementally, took out my entire realm
To be clear up front: this was my mistake, not a Keycloak defect.
I was trying to enable refresh-only for a single client, just for testing. I scoped a policy with a
client-attributescondition on thedpop-bind-enforcerexecutor, so it would only apply to clients withdpop.refresh.only: trueset. Two things went wrong:The idea
Conceptually: allow refresh-token-only binding to be enabled per client, the same way full DPoP binding already is — off by default, no effect on clients that don't opt in, and enforced in core in a binding-aware way:
Why this fits the "keep it simple" philosophy
This isn't a new capability so much as removing an inconsistency: full DPoP is per-client; refresh-only isn't. Making them symmetric arguably makes the feature easier to understand and adopt, and gives operators a safe, incremental path rather than an all-or-nothing switch.
Also, from a very biased point of view, it makes DPoP in general easier to adopt - first roll out refresh token only incrementally, then (when your backend supports) roll out full DPoP incrementally too.
Questions for maintainers
All reactions