Expected Behavior
I had legacy OAuth2 authorization server which I can't change, it issues Access token without expiration time and without Refresh token.
When I use reactive WebClient with ServerOAuth2AuthorizedClientExchangeFilterFunction filter I would expect just one call for authorization service and reusing token for further requests (until token expiration, 403 response or any other failure occurs).
Current Behavior
Due to missing expire time in token body (which in my case is implicit and defaults to 1 hour) Spring doesn't try to reuse previous token (which I don't like but I can understand) but also always send two Access token requests, one for authorize, and second for reauthorize of "expired" token.
Further explanation, when token doesn't have expire time set it is defaulted by Spring to 1 second and that with default clock skew of 1 minute means that token never can be valid from Spring perspective and re-authorization will always happen (despite resulting in exactly identical expire-in-one-second token).
In summary, I would expect that: 1) in case no expiration time is present and when Refresh token is not provided then token is not reauthorized at all. This could be also expanded to scenario where expiration token is present but is smaller than specified clock skew. 2) in case Spring Security don't know how long token is valid then it should try to use it anyway 3) (optional) some assertion/warning message should inform developer about possible mis-configuration, as last resort note in docs would be helpful.
See related code: ServerOAuth2AuthorizedClientExchangeFilterFunction.java#L340-L351 PasswordReactiveOAuth2AuthorizedClientProvider.java#L95-L108 ClientCredentialsReactiveOAuth2AuthorizedClientProvider.java#L71-L83
Context
Because of this issue unnecessary requests are made which is confusing when looking at logs and it is hard to track down actual root cause.
My current workaround was to write custom body extractor based on OAuth2BodyExtractors.oauth2AccessTokenResponse(), once payload is parsed I use builder OAuth2AccessTokenResponse.withResponse() to add expiration time manually. This works fine but amount of boilerplate I need to provide to make this solution work is too big for scenario which is de facto complaint with actual OAuth standard.
If solution I proposed above is too invasive then I would opt for defaultExpirationTime on Provider config, then I can just put one YML line without calling heavy guns.
Comment From: jgrandja
@kkurczewski I understand the issue you are having. It's unfortunate the expires_in parameter is not being returned from the AS.
The spec states the following:
expires_in RECOMMENDED. The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will expire in one hour from the time the response was generated. If omitted, the authorization server SHOULD provide the expiration time via other means or document the default value.
Given that you know the default...
Due to missing expire time in token body (which in my case is implicit and defaults to 1 hour)
Your solution makes sense...
My current workaround was to write custom body extractor based on
OAuth2BodyExtractors.oauth2AccessTokenResponse(), once payload is parsed I use builderOAuth2AccessTokenResponse.withResponse()to add expiration time manually.
An alternative solution would be to customize the legacy OAuth2 authorization server to return the expires_in, which would be ideal.
I'm reluctant to apply any of the changes you suggested as most AS do return the expires_in parameter.
I'm going to close this issue but if you feel strongly on another proposed enhancement then we can discuss this further.
Comment From: suleymangezsat
Clearly, we are facing the same issue. AS does not provide the expires_in value. However, I believe sending a re-authorize request is unnecessary in this scenario since the authentication was already completed in the initial request. This is a bug that needs to be addressed. Furthermore, as previously mentioned, in situations where expires_in is absent and the token's expiration is unknown, it should be feasible to assign a default expiration value.
Comment From: easterk
We have the same issue with a large vendor not returning expires_in. I'd also like to see this handled in Spring.