Skip to content

Add support for OAuth 2.0 Attestation-based client authentication#47962

Merged
mposolda merged 7 commits intokeycloak:mainfrom
tdiesler:ghi43136
Apr 20, 2026
Merged

Add support for OAuth 2.0 Attestation-based client authentication#47962
mposolda merged 7 commits intokeycloak:mainfrom
tdiesler:ghi43136

Conversation

@tdiesler
Copy link
Copy Markdown
Contributor

@tdiesler tdiesler commented Apr 10, 2026

closes #48265

@tdiesler tdiesler force-pushed the ghi43136 branch 3 times, most recently from 6374b0c to 3ce4b68 Compare April 16, 2026 14:44
Copy link
Copy Markdown
Contributor

@mposolda mposolda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tdiesler Thanks! Added some inline comments. Can you check please?

TODOs are fine as we can address them hopefully in the follow-up PRs.

execution.setParentFlow(clients.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setAuthenticator("attestation-based");
execution.setPriority(5);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
execution.setPriority(5);
execution.setPriority(60);

Is it possible to use priority 60, so that this new authenticator is added after existing ones (also note other authenticators that they increase by 10)

Copy link
Copy Markdown
Contributor Author

@tdiesler tdiesler Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can unfortunately not do that at the moment. AFAIU, the first (alternative) client authenticator that succeeds determines the outcome. e.g. if client secret passes, abca is not checked

ABCA is the stronger authorization mechanism - this is checked by OpenID conformance.

I have more details on how this plays with public clients and direct access grants if you care to know.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, ok. Maybe we can discuss that on one of next meetings? At least for this PR it should be OK since the feature is experimental.

// when AttestationBasedClientAuthenticator is processed
//
String abcaAuthType = AttestationBasedClientAuthenticator.PROVIDER_ID;
if (client.isPublicClient() && factory.getId().equals(abcaAuthType) && Profile.isFeatureEnabled(Profile.Feature.CLIENT_AUTH_ABCA)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is really needed? I assume that if client is authenticating with ABCA, it should be always confidential client? Is it possible to make sure that only confidential clients authenticate with ABCA?

Public clients are not supposed to authenticate with any client authentication methods. They usually use just client_id parameter to "identify" themselves in the request andfor this purpose, we just use expectedClientAuthType set to KeycloakModelUtils.getDefaultClientAuthenticatorType() .

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was indeed a change in the spec from “mechanism for public clients to authenticate” to “general client authentication mechanism”. The draft decoupled itself from OAuth’s client-type distinction entirely - it effectively introduces a third model: attested clients (orthogonal to public/confidential).

This shift aligns with the underlying idea: attestation provides hardware/software-backed proof which can substitute:

  • secrets (confidential clients)
  • or lack thereof (public clients)

Without this change, the AttestationBasedClientAuthenticator is not called for confidential clients. I added a test thereof.

Note, an invalid abca request or missing attester pub key currently fails in the AttestationBasedClientAuthenticator but is silently ignored because some other ClientAuthenticator succeeds - this might be a TODO

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, more details would be good. Maybe we can discuss on one of our next meetings?

Just to doublecheck: Is it this specification draft, which you talk about https://datatracker.ietf.org/doc/html/draft-ietf-oauth-attestation-based-client-auth-08 ?

This change is OK in this PR, but we possibly need to figure this as a follow-up to avoid directly referencing the concrete authenticator from ClientAuthenticationFlow .

Copy link
Copy Markdown
Contributor Author

@tdiesler tdiesler Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is actually https://www.ietf.org/archive/id/draft-ietf-oauth-attestation-based-client-auth-07.html
which I have bookmarked. I could however not find a reference to it in https://openid.net/specs/openid4vc-high-assurance-interoperability-profile-1_0-final.html @thomasdarimont could you perhaps confirm the exact abca spec version that we ought to use?

if (!normalize(new URI(t.getHttpUri())).equals(normalize(uri)))
throw new DPoPVerificationException(t, "DPoP HTTP URL mismatch");
URI dpopUri = new URI(t.getHttpUri());
if (!normalize(dpopUri).equals(normalize(uri))) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this change should not be needed and may have security implications.

For the setup of Keycloak behind reverse proxy, you may take a look on how to configure hostname. See the "Server" guides from https://www.keycloak.org/guides . Especially https://www.keycloak.org/server/hostname or https://www.keycloak.org/server/reverseproxy might be probably useful.

Also not sure if you are aware, but there is environment in the Keycloak org, which is maintained mostly by @tnorimat and which contains some support for running conformance tests: https://github.com/keycloak/keycloak-oauth-sig .

For example see here how Keycloak is configured (there are other config files as well): https://github.com/keycloak/keycloak-oauth-sig/blob/main/conformance-tests-env/docker-compose.yml#L159

Copy link
Copy Markdown
Contributor Author

@tdiesler tdiesler Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already set the hostname

start-dev
--hostname=https://keycloak.nessustech.io:8443
--proxy-headers=xforwarded
--features=oid4vc-vci,oid4vc-vci-preauth-code,client-auth-abca
--bootstrap-admin-username=admin
--bootstrap-admin-password=*****
--db=postgres
--db-url=jdbc:postgresql://localhost:32543/keycloak
--db-username=postgres
--db-password=*****
--log-level=org.keycloak.protocol.oid4vc:debug,org.keycloak.services:debug,org.keycloak.events:debug,org.keycloak.authentication:debug,root:info

Can we perhaps investigate this separately?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would be good to investigate this separately. But will be good if you can remove this change from this PR as it is not directly related to OAuth 2.0 attestation-based client auth, but seems rather as a setup issue.

DPoP is supported feature and should not be updated to possibly insecure way due your (maybe incorrect) setup of hostname.

Do you have a chance to try https://github.com/keycloak/keycloak-oauth-sig and compare with your setup?

public List<ProviderConfigProperty> getConfigProperties() {
return List.of();
ProviderConfigProperty jwks = new ProviderConfigProperty();
jwks.setName(OAUTH_CLIENT_ATTESTATION_CONFIG_ATTESTER_JWKS);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is temporary (as we briefly discussed on the meeting yesterday) and should probably identity provider mechanisms similarly like for example CLIENT_AUTH_FEDERATED is using (See for example FederatedJWTClientAuthenticator and related classes for the inspiration).

But it is probably fine this way for the initial PRand we can update later.

Copy link
Copy Markdown
Contributor

@IngridPuppet IngridPuppet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new concerns in addition to Marek's. All my previous concerns were addressed.

@tdiesler tdiesler force-pushed the ghi43136 branch 5 times, most recently from 872349d to 55d81b5 Compare April 17, 2026 09:59
Copy link
Copy Markdown
Contributor

@mposolda mposolda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tdiesler Thanks for the latest updates! LGTM if you remove the DPoP related change.

Other issues can be possibly addressed as a follow-ups.

execution.setParentFlow(clients.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setAuthenticator("attestation-based");
execution.setPriority(5);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, ok. Maybe we can discuss that on one of next meetings? At least for this PR it should be OK since the feature is experimental.

// when AttestationBasedClientAuthenticator is processed
//
String abcaAuthType = AttestationBasedClientAuthenticator.PROVIDER_ID;
if (client.isPublicClient() && factory.getId().equals(abcaAuthType) && Profile.isFeatureEnabled(Profile.Feature.CLIENT_AUTH_ABCA)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, more details would be good. Maybe we can discuss on one of our next meetings?

Just to doublecheck: Is it this specification draft, which you talk about https://datatracker.ietf.org/doc/html/draft-ietf-oauth-attestation-based-client-auth-08 ?

This change is OK in this PR, but we possibly need to figure this as a follow-up to avoid directly referencing the concrete authenticator from ClientAuthenticationFlow .

if (!normalize(new URI(t.getHttpUri())).equals(normalize(uri)))
throw new DPoPVerificationException(t, "DPoP HTTP URL mismatch");
URI dpopUri = new URI(t.getHttpUri());
if (!normalize(dpopUri).equals(normalize(uri))) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would be good to investigate this separately. But will be good if you can remove this change from this PR as it is not directly related to OAuth 2.0 attestation-based client auth, but seems rather as a setup issue.

DPoP is supported feature and should not be updated to possibly insecure way due your (maybe incorrect) setup of hostname.

Do you have a chance to try https://github.com/keycloak/keycloak-oauth-sig and compare with your setup?

@tdiesler
Copy link
Copy Markdown
Contributor Author

tdiesler commented Apr 17, 2026

Reverse unrelated DPoP changes

Copy link
Copy Markdown
Contributor

@mposolda mposolda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tdiesler Thanks!

@IngridPuppet Thank you as well for the review!

FYI. I've created dedicated issue #48265 for this PR and added it as subtask to #43136 (parent issue having other ABCA related issues).

Also will create some follow-up GH issues for additional things discussed in the PR review of this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ABCA] Introduce initial authenticator and building-blocks

4 participants