Closed jgrandja closed 9 months ago
Hi,
This feature is removed from the 5.2.x milestone. May I know if any plan about this feature? Many thanks.
Thanks and regards, William
@William1104 We are planning on implementing this feature but it may be too early at the moment until the spec goes through the review process further.
Instead we replaced this feature with #6053. As an FYI, you can also exchange a JWT token for another JWT using the JWT Bearer grant.
I'm curious, are you aware of any providers that have implemented OAuth 2.0 Token Exchange?
@jgrandja
I'm curious, are you aware of any providers that have implemented OAuth 2.0 Token Exchange?
One example would be Keycloak (https://www.keycloak.org/):
Token exchange in Keycloak is a very loose implementation of the OAuth Token Exchange specification at the IETF (https://www.keycloak.org/docs/6.0/securing_apps/#_token-exchange).
@jgrandja I am facing this problem where spring security isn't sending the scopes to auth server (azure). I traced it back to OAuth2AuthorizationCodeGrantRequestEntityConverter.java
which is ignoring the "scope" and "resource" parameters that are present in the request parameter in that class.
Is there a way I can override this behavior?
@kdhindsa The issue you are having is not related to this issue (Token Exchange). Please post this question on StackOverflow or log a new issue if you believe this is a bug. Please see guidelines on using GitHub Issues.
spring security isn't sending the scopes to auth server (azure)
Have you configured the scopes
property for the ClientRegistration
?
Please see the reference doc for more details. I suspect there is a misconfiguration.
@jgrandja, yes, I had configured the scopes correctly:
spring.security.oauth2.client.registration.azure.scope=openid,user.read,offline_access,files.read.all
but that didn't work. Eventually I found this configuration:
http.oauth2Login()
.tokenEndpoint()
.accessTokenResponseClient(aadAccessTokenResponseClient());
So I ended up creating my custom response client service which manually injects scopes:
public class AADOAuth2AuthorizationCodeGrantRequestEntityConverter
extends OAuth2AuthorizationCodeGrantRequestEntityConverter {
@Override
public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
RequestEntity requestEntity = super.convert(authorizationCodeGrantRequest);
LinkedMultiValueMap<String, String> params = (LinkedMultiValueMap<String, String>)requestEntity.getBody();
// FIXME: read from config
params.put("scope", Arrays.asList("openid user.read offline_access files.read.all"));
params.put("resource", Arrays.asList("https://graph.microsoft.com"));
return requestEntity;
}
}
and that worked.
Hi @jgrandja, after lots of draft versions, the corresponding RFC 8693 standard for token exchange has finally been published this week (https://tools.ietf.org/html/rfc8693). So it would be great if you could schedule this in one of the next milestones.
Thanks for the heads up @andifalk. I don't think we'll be able to get this into 5.3 (due Mar 4) as we have other priority tasks that need to be completed. We'll likely target 5.4
This issue seems quite old now... Is this feature still in the roadmap for Spring Security?
https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16
@emedina RFC 8693 was just published in Jan 2020, as mentioned in this comment. Now that it's published, we will see which providers implement to determine the appropriate time to implement on our end.
At the same time, features will get implemented quicker by the community via PR's as our team only has so much bandwidth. As of now, this feature is not scheduled for 5.4 but if a PR comes in then we will consider it then.
@jgrandja Does this issue get resolved now?
@ZxShirley It's not scheduled as of yet. As mentioned in my previous comment...
...features will get implemented quicker by the community via PR's as our team only has so much bandwidth. As of now, this feature is not scheduled for 5.4 but if a PR comes in then we will consider it then.
We'll be prioritizing features when we plan for 5.5, which will be towards end of this month.
Any update on when this will be prioritized as RFC 8693 was been defined for over a year now? Many providers are supporting this now such as keycloak, ping federate, etc.
@muskiehunter1985 We won't be able to get this into 5.5.0
.
However, I've scheduled it for 5.6.0
, which should be released around Nov 2021.
We'll aim to get it into the 5.6.0-M1
release.
Does this issue cover the urn:ietf:params:oauth:token-type:access_token
subject token type ? If so, the current implementation of JwtBearerOAuth2AuthorizedClientProvider
would be near the same. Only the Converter would map assertion to subject_token parameters. And subject_token_type and grant_type should be given accordingly.
Jwt Bearer may works well but, as per the RFC, the AS must validate the audience claim value is its token endpoint uri. This block all use cases where a microservice would request a new token on behalf of the current request token (audience is the microservice itself not the AS), which, IMHO is a far more common use case than requiring the microservice to forged/signed a Jwt Bearer for a given subject.
Unfortunaletly, some classes are not public and make difficult to write its own provider (OAuth2AuthorizationGrantRequestEntityUtils
or AbstractOAuth2AuthorizationGrantRequestEntityConverter
). As an example, https://github.com/jgrandja/oauth2-protocol-patterns/blob/main/microservice-b/src/main/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverter.java won't compile anymore.
This is not specific to this issue as OAuth2 is an extensible framework and it's not an unusual use case to implement its own grant type.
@scrocquesel
Does this issue cover the
urn:ietf:params:oauth:token-type:access_token
Yes. It's defined in Section 3. Token Type Identifiers
As an example,
...JwtBearerGrantRequestEntityConverter.java
won't compile anymore
The oauth2-protocol-patterns
sample has not been upgraded to Spring Security 5.5 as of yet and this class will be removed when updated which will resolve the compile error.
Is this still scheduled it for 5.6.0, which should be released around Nov 2021?
@mmo21 Unfortunately, this won't make it into 5.6
. We've been quite busy with Spring Authorization Server this year and there hasn't been anyone from the community to contribute to this.
Hi, @kdhindsa, @jgrandja.
@jgrandja, yes, I had configured the scopes correctly:
spring.security.oauth2.client.registration.azure.scope=openid,user.read,offline_access,files.read.all
but that didn't work. Eventually I found this configuration:
http.oauth2Login() .tokenEndpoint() .accessTokenResponseClient(aadAccessTokenResponseClient());
So I ended up creating my custom response client service which manually injects scopes:
public class AADOAuth2AuthorizationCodeGrantRequestEntityConverter extends OAuth2AuthorizationCodeGrantRequestEntityConverter { @Override public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) { RequestEntity requestEntity = super.convert(authorizationCodeGrantRequest); LinkedMultiValueMap<String, String> params = (LinkedMultiValueMap<String, String>)requestEntity.getBody(); // FIXME: read from config params.put("scope", Arrays.asList("openid user.read offline_access files.read.all")); params.put("resource", Arrays.asList("https://graph.microsoft.com")); return requestEntity; } }
and that worked.
About this topic, I created an new issue: https://github.com/spring-projects/spring-security/issues/10452
My humble attempt at a workaround : https://github.com/paulceli/spring-token-exchange-keycloak
I'm looking at this and I'd like a little bit of guidance as to how to interpret the specification and how to follow Spring Security's conventions.
My uncertainties revolve around the two flavors of examples seen in the spec, under 2.3 Example Token Exchange, and under Appendix A. Additional Token Exchange Examples, respectively.
subject_token_type
is urn:ietf:params:oauth:token-type:access_token
and the access token from the authenticated user is being passed directly to the subject_token
request parameter.subject_token
, along with a subject_token_type
equal to urn:ietf:params:oauth:token-type:jwt
.actor_token
and actor_token_type
(delegation, rather than impersonation.)My problem is figuring out how to choose which one of these methods to use for subject_token
, and also, when to send the actor_token
/actor_token_type
request parameters, and when not to. I can only assume that all of this is up to the resource server, but how should Spring Security decide which strategy to use?
subject_token
from the Authorization
header, and don't send any actor_token
.subjectTokenType
and actorTokenType
in the ClientRegistration
class, and based on their values, choose a strategy? For example, if ClientRegistration.subjectTokenType
is urn:ietf:params:oauth:token-type:access_token
, we pass on the token we got in the Authorization
header, and if it's urn:ietf:params:oauth:token-type:jwt
, we generate our own JWT.Hopefully this hasn't been too much rambling from my part.
(PS: I can't guarantee that my efforts will actually amount to anything in the end, but either way, the questions I raise here will maybe help others working on this issue.)
I'd like some feedback as to whether or not I'm going in the completely wrong direction here:
Is it okay to introduce new static methods in ServletOAuth2AuthorizedClientExchangeFilterFunction
for easily adding audience
and resource
values to the request context, just like the pre-existing ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(String)
and ServletOAuth2AuthorizedClientExchangeFilterFunction.authentication(Authentication)
methods?
For example, something along the lines of this:
public static Consumer<Map<String, Object>> audience(String audience) {
return (attributes) -> attributes.put(OAuth2AuthorizationContext.AUDIENCE_ATTR_NAME, audience);
}
And just to give an example of how it might look when setting up the WebClient
call:
webClient
.get()
.uri("/rest/v1/lastname")
.accept(MediaType.APPLICATION_JSON)
.attributes(clientRegistrationId("lastname-api-client"))
.attributes(audience("lastname-api")) // <- new
.attributes(resource(URI.create("http://localhost:8083"))) // <- new
// ...
If not, what's the preferred way of passing additional information into the token exchange request? Reading between the lines, I've come to the conclusion that adding anything at all to ClientRegistration
is probably not desirable, so this was the best alternative I could find so far.
@ThomasKasene I haven't had a chance to re-review the spec lately. I appreciate that users are looking for this feature but we've been very busy with many other priorities and we only have so much bandwidth. No one has offered to take this feature on so this slows things down as well.
Is it okay to introduce new static methods in
ServletOAuth2AuthorizedClientExchangeFilterFunction
for easily addingaudience
andresource
values to the request context
No. This would not be the approach we would use.
We would most definitely introduce a new OAuth2AccessTokenResponseClient
and OAuth2AuthorizedClientProvider
that ultimately would be composed in OAuth2AuthorizedClientManager
.
I've come to the conclusion that adding anything at all to
ClientRegistration
is probably not desirable
Correct.
I'm slowly chipping away at the spec/implementation as I find the time, and I recently discarded the idea of manipulating the ExchangeFilterFunction
. I have added such methods to my OAuth2AuthorizedClientProvider
as there seems to be a definite need for them, however. But we'll see how it all turns out in the end. Anyway, I appreciate you taking the time to let me know!
Currently, my biggest obstacle is figuring out how to make it create and sign the actor token. I couldn't find any easily re-usable code which does this, so I was forced to draw inspiration (mostly copy) from NimbusJwtClientAuthenticationParametersConverter
.
@ThomasKasene Please reach out to me in January as we should have our 6.1
plan in place. If this is scheduled for 6.1
then contributions would be very helpful.
I'll do that. In the meantime, I've got a couple more questions to ponder:
OAuth2AuthorizedClient
s with the current APIs (OAuth2AuthorizedClientRepository
, etc)?client_credentials
grant, and with the token-exchange
grant, where it uses itself as the actor? Those principals would be identical (I think), and with matching clientRegistrationId
s they would be difficult for the OAuth2AuthorizedClientRepository
implementations to distinguish.org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType.N_A
, to represent the value registered as part of RFC8693?@ThomasKasene I won't be able to answer your questions until I review the spec in detail, which won't happen until after the new year.
Any news on this topic? in which version, we could expect this?
There's no news from me, life happened and I've been somewhat absent these last few months.
The specification is a very flexible one, and it's difficult to shoehorn the entire thing into Spring Security without making some changes to several of the existing APIs, at least from my limited understanding of Spring Security. The main complexity comes from the fact that a user should be able to authorize up to several other users (or applications) to act on their behalf, so the model becomes something like this:
principal + client registration ID + actor1 -> OAuth2AuthorizedClient
(1)
principal + client registration ID + actor2 -> OAuth2AuthorizedClient
(2)
principal + client registration ID + actor3 -> OAuth2AuthorizedClient
(3)
Whereas the APIs within Spring Security only allows for this model:
principal + client registration ID -> OAuth2AuthorizedClient
(I'm hoping somebody smarter than me will correct me if I've misunderstood these APIs!)
@jgrandja, Any news regarding adding it into the upcoming milestones? Almost 3 years left since the time you promised to include it..
you promised to include it
tbf: that's not fair; I don't think the spring security team has promised it, from my understanding it was under considerations, but priorities shift...
Which provider do you intend to use?
I / the project I am in considered it for ADFS and Keycloak, but neither implements the RFC yet and have their own protocols (though keycloak's token exchange is inspired by the RFC, but as they say in the docs, it's a very loose implementation).
@ciis0 Thank you for coming to my defense :smile:
@uladzimir-shymanski There were no promises from my end. In fact, we as a team ensure that we don't make promises for features in a specific release. As @ciis0 mentioned, priorities are constantly changing so feature requests may get pushed out.
Furthermore, there is at most 2 resources on the Spring Security team that work on the OAuth support. I'm dedicated full time but I'm also leading Spring Authorization Server which takes most of my time. @sjohnr is the other resource which has been a huge help but he also works on other parts of Spring Security. So I hope you can appreciate we have limited resources devoted to the OAuth support within the Spring Security projects. And as I have mentioned here and here, no one from the community has submitted a PR for this feature, so it continues to wait in the backlog.
If you are eager for this feature, I have a task for you if you are up for it? Before we integrate this feature into the codebase, we need to do some preliminary research on which providers have actually implemented the server-side support for this RFC. Are you able to compile this list for us?
If there are many providers that have implemented the server-side of this RFC, this feature may be scheduled sooner. If there are only a couple of providers that have implemented, then it will continue to wait in the backlog.
In our case, we use Ping Federate, with private_key_jwt
for the client assertion. As far as I know there are several providers that support this grant.
https://docs.pingidentity.com/r/en-us/pingfederate-113/pf_config_oauth_token_exchange
At the moment, we are usign a custom library that add filter to the WebClient (acts before the actual call to get the access-token). It could be great to build on top of Spring Security for this functionality.
@jgrandja The list of providers implementing token exchange (rfc8693) is growing. Here is a first quick list of some well-known providers:
As of now, none of the public cloud providers (Azure AD, AWS Cognito, Google Cloud Identity) offer any support for rfc8693 so far.
@ThomasKasene @jgrandja - Is it currently possible to implement token exchange at all at present, even by using a custom provider etc as documented in the "How-to: Implement an Extension Authorization Grant Type" document?
I have been giving it a go, but running into confusion about how ClientRegistration fits into it, given all the provided builders used in other provider classes are making this assumption of an existing registration.
I think token exchange might not be a client registration but something you enable on the interceptor.
For Keycloak I implemented it like this: https://gist.github.com/ciis0/d0bcd5d0a51428a4192b598ee369b3b5
Likely something similar "simply" needs to be added to the built-in interceptors.
@ciis0 - we have no concept of a client registration. In my use case my access token I want to exchange comes from some other IDP of which we have multiple, we want to exchange it for a common identity to use within the platform. This way all the microservices dont need to implement logic for 5 different types of identity provider.
So lets say forgerock issues the access token, it has a set of basic claims to the system. This is passed in at the API gateway, verified and exchanged for an internal identity token that can be passed to multiple back end services.
hmm, are you aware this issue is about implementing Token Exchange as described in RFC 8693?
I am having trouble applying the RFC to your use-case; the RFC from my understanding "just" describes an extension to the OAuth Token Endpoint to exchange tokens from one audience/scope for another audience/scope; but in my understanding "only" between a client and one Authorization Server / Secure Token Server; it's no abstraction for one a client and multiple AS/STS.
@Milesy Questions are better suited to Stack Overflow. We prefer to use GitHub issues only for bugs and enhancements. Please post your question there.
hmm, are you aware this issue is about implementing Token Exchange as described in RFC 8693?
I am having trouble applying the RFC to your use-case; the RFC from my understanding "just" describes an extension to the OAuth Token Endpoint to exchange tokens from one audience/scope for another audience/scope; but in my understanding "only" between a client and one Authorization Server / Secure Token Server; it's no abstraction for one a client and multiple AS/STS.
I think this is correct. Token exchange is an extension for OAuth server, not a client-side thing. So if it's implemented in Spring ecosystem then it should be implemented maybe in Spring Auth Server.
@ciis0 - we have no concept of a client registration. In my use case my access token I want to exchange comes from some other IDP of which we have multiple, we want to exchange it for a common identity to use within the platform. This way all the microservices dont need to implement logic for 5 different types of identity provider.
So lets say forgerock issues the access token, it has a set of basic claims to the system. This is passed in at the API gateway, verified and exchanged for an internal identity token that can be passed to multiple back end services.
For very similar use case I've used Keycloak as the "main" auth server, which is then configured to delegate to external IdP systems and allow token exchange etc. Spring cloud gateway which stands in front of the system has Spring security configured to point to the Keycloak, and passes its own JWT tokens downstream, so services have simple security config which also points to Keycloak and expects JWT. If services need to call external APIs then gateway has filters which request remote tokens using token exchange configured in Keycloak. If you follow similar pattern then to replace Keycloak with Spring auth server (if needed) I think token exchange should be implemented in this auth server (because it's a part of OAuth spec/flow), and auth clients (applications) shouldn't have this logic inside somehow.
Token exchange is an extension for OAuth server, not a client-side thing. So if it's implemented in Spring ecosystem then it should be implemented maybe in Spring Auth Server
While I agree the server side would be implemented in Spring Authorization Server, I'd say there still is a client part -- having one token, getting another one -- which would be implemented in Spring Security (like other iteraction with the token endpoint, like Client Credentials or Refresh Token grant.)
I think the challenge is where to implement the client part. My ad-hoc implementation suggest at least the interceptors need to be aware of what the target audience/scopes is/are and use common logic (the TokenExchangeClient in my code) to actually exchange the token.
My client relies on the Authorized Client classes, which suggest that the common logic could be integrated there, maybe OAuth2AuthorizeRequest
could be extended with attributes to enable token exchange? I think primarily about target audience/scope attributes, not sure about secondary ones.
@andifalk Thank you for providing the list of supporting providers.
Based on that, we will provide support for RFC 8693 OAuth 2.0 Token Exchange in Spring Security (client-side) and Spring Authorization Server (server-side).
However, I'm not sure yet if we can provide this new support in Spring Security 6.3
and Spring Authorization Server 1.3
.
There are a couple of higher priority items that we need to deal with first and depending on when those complete we can then decide if this feature will get into the next release.
All I can say now is that we will do our absolute best and keep you all updated as we progress over the current release cycle.
Initial support is added via 85c3d0ab13212b6df710207024b7240c71d2ebef. I've opened gh-14698 as a follow-up to add reference documentation, and hope to have a blog post available shortly as well.
We need to provide support for OAuth 2.0 Token Exchange RFC 8693
Related #6053