-
Notifications
You must be signed in to change notification settings - Fork 6k
Missing support for private_key_jwt in ClientRegistrations #9780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@ThomasKasene It seems you are missing |
I already tried with both (By the way, isn't the client registration smart enough to auto-configure itself with the |
Related gh-9795 |
Thanks for reporting this bug @ThomasKasene. In the meantime, you could workaround this by not configuring the Also, could you please log a separate ticket for the issue related to |
One way to do that is to override the @Bean
InMemoryClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) {
Collection<ClientRegistration> propertyBackedClientRegistrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties).values();
ClientRegistration maskinportenClientRegistration = ClientRegistration
.withRegistrationId("maskinporten-private-key-jwt")
.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
.scope("test:test")
.tokenUri("https://ver2.maskinporten.no/token")
.clientId("1337")
.clientName("Maskinporten")
.build();
List<ClientRegistration> registrations = new ArrayList<>(propertyBackedClientRegistrations.size() + 1);
registrations.addAll(propertyBackedClientRegistrations);
registrations.add(maskinportenClientRegistration);
return new InMemoryClientRegistrationRepository(registrations);
} I'm not sure what you mean by "the issue related to |
Yes that is one way, but the way I suggested is to simply define the Boot properties without defining
Regarding your comment:
Looks like only |
No, issuer metadata exposes what the authorization server support not what a client must use. Client will use the metadata depending on what grant type and client authentication method they want to use. On the other hand, it would be possible to check upfront that the client configuration will eventually fail to request a token due to unsupported configuration but I'm not convince it worth it. I haven't seen any client implementation doing that kind of check. metadata is often only used to get endpoint uri.
It works for me with jwt-bearer grant type and a custom grant type. I use JWK keys. |
Of course, that is much easier!
Are you referring to this bit in if (grantTypes != null && !grantTypes.contains(GrantType.AUTHORIZATION_CODE)) {
throw new IllegalArgumentException(
"Only AuthorizationGrantType.AUTHORIZATION_CODE is supported. The issuer \"" + issuer
+ "\" returned a configuration of " + grantTypes);
} If so, I'll make sure to create a new issue for that! I just hadn't gotten to that part yet.
That is fair enough - an argument miiight be made for servers that only support one of each, but what, then, if they suddenly added support for one more?
I might be going a little bit off-topic here, but do you have an example of how you do this? I don't find the documentation all that helpful as it doesn't give me any anchor points as to where to add the code. At the moment I'm trying to add my JWK resolver in a highly convoluted |
Yes. If you could create a new issue for this that would be great. |
This should get you start : https://gist.github.com/scrocquesel/d38549c64837f8eeae3d4619c850ba60 May I ask you what will be the flow of your token ? Specificaly, from where will you get the token you will pass to the maskinporten AS ? |
Thanks a bunch for the sample. It looks as though I wasn't very far off myself, but I'm still not getting it to work.
Certainly! For my particular case I'm hoping to do a machine-to-machine call, so no user involvement. This means that I don't have a if (!(context.getPrincipal().getPrincipal() instanceof Jwt)) {
return null;
} The above check happens before the call to Just for some background, I'm trying to set up an interceptor for my public class MaskinportenAuthInterceptor implements ClientHttpRequestInterceptor {
// ...
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
ClientRegistration maskinporten = clientRegistrationRepository.findByRegistrationId("maskinporten-private-key-jwt");
Jwt jwt = Jwt.withTokenValue("dummy")
// ...
// This is not really needed in the assertion to Maskinporten, but
// JwtAuthenticationToken uses it to set its name-attribute.
.subject("dummy-subject")
.build();
// Not convinced JwtAuthenticationToken is the correct implementation to use here.
JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt);
OAuth2AuthorizedClient client = authorizedServiceClientManager.authorize(OAuth2AuthorizeRequest
.withClientRegistrationId(maskinporten.getRegistrationId())
.principal(principal)
.build());
request.getHeaders().setBearerAuth(client.getAccessToken().getTokenValue());
return execution.execute(request, body);
}
} Any pointers as to whether or not this is completely wrong are greatly appreciated! |
You are really close and you have the same issue as me. Currently, JwtBearer grant type don't allow to easily pass a custom Jwt. Which is the whole purpose of this grant type as per the rfc. You may found some clue at the end of #9812 I ended up copy/pasting the JwtBearerOAuth2AuthorizedClientProvider as it is not really usefull in its current form to allow to pass a jwt converter. This allow to reuse the signing mechanism and only generate the Jwt when required which is not possible with the current implementation. |
Have you checked the reference documentation? |
The See Section 2.1. Using JWTs as Authorization Grants:
Take note of the bold highlight...an access token request is sent to the Authorization Server's token endpoint to exchange the passed Since you are looking to perform machine-to-machine (service-to-service) call, then you need the capability to create a |
The spec says:
I'm not an english expert, but this seems to means that the client should build a request to get an access token as defined in the section 4 of RFC7521 (grant_type, assertion, scope), not that the assertion of the access token request is actually an access token. @ThomasKasene For a machine-to-machine call with no user involved you can use the client_id as the sub claim of the Jwt you will build. This should be equivalent to a client credentials grant flow.
This is often used without the signed client_assertion authentication as they are very similar but it depends of how the AS is actually doing the check. The AS I use require the client to authenticate with a jwt bearer for every grant flow. To ease that, you may actually use a String body = webClient
.get()
.attributes(authentication(new ClientActingOnBehalfOfItSelf ()).andThen(clientRegistrationId("test")))
.retrieve()
.bodyToMono(String.class)
.block(); This allow to use a default client registration without the client code to know the actual client id to use. |
@ThomasKasene, did you manage to use private_key_jwt instead of clientSecret for ID-porten as well (not just Maskinporten)? I'm getting "Client authentication failed. No client authentication included.". I have tried to follow these instructions: The accessTokenResponseClient is called, but not jwkResolver. I'm sent to the auth provider, but get the error when I'm sent back to the application. Tried to configure the following in my OpenIdConnectConfig:
|
@erlendfg alas, no - I haven't attempted to use ID Porten myself yet. I know you didn't ask, but for anybody else who might be in the same pickle: As for Maskinporten, I gave up on using Spring Security's support for it. I realized that I would have to customize so much of Spring Security, make so many subclasses and implementations of stuff™, that in the end it just wasn't worth it. This is in part due to Spring Security's rigidity/commitment to follow the specs only, and partly because Maskinporten doesn't follow said specs precisely, meaning that shoehorning my Maskinporten-integration into Spring Security proved to be a difficult task. Instead, I implemented a single @RequiredArgsConstructor
public class MyResourceServiceAuthenticationInterceptor implements ClientHttpRequestInterceptor {
private final MaskinportenService maskinportenService;
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().setBearerAuth(maskinportenService.fetchAccessToken());
return execution.execute(request, body);
}
} ... which I added to the public MyResourceServiceConstructor(RestTemplateBuilder restTemplateBuilder,
MyResourceServiceAuthenticationInterceptor authenticationInterceptor) {
this.restTemplate = restTemplateBuilder
.interceptors(authenticationInterceptor)
.build();
} |
Describe the bug
I'm trying to set up an OAuth2 client against
https://ver2.maskinporten.no/
, whose .well-known configuration endpoint looks like this:I figured I'd try to use the new support for
private_key_jwt
in Spring Security 5.5.0 (in combination with Spring Boot 2.4.5), but when I start the application I get an error:To Reproduce
I try the following configuration in my application.yml, just to get off the ground:
Expected behavior
I expected this part of the client setup to work "out of the box", but there appears to be something missing from
org.springframework.security.oauth2.client.registration.ClientRegistrations.getClientAuthenticationMethod
, maybe? Unless I'm not supposed to have reached that code path at all, in which case I don't know what to expect.Sample
I put together a small, reproducible sample which uses Maven, hopefully that's okay.
The text was updated successfully, but these errors were encountered: