Skip to content

token endpoint: align response with old spring-security-oauth #1108

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

Closed
emilburzo opened this issue Mar 1, 2023 · 4 comments
Closed

token endpoint: align response with old spring-security-oauth #1108

emilburzo opened this issue Mar 1, 2023 · 4 comments
Assignees
Labels
status: invalid An issue that we don't feel is valid

Comments

@emilburzo
Copy link

Describe the bug
I'm migrating from the EOL spring-security-oauth project and thus need to keep the responses identical.

I've started with the token endpoint and it's working well so far, however there are two differences that I do not know how to address:

For the token endpoint, the differences are:

  • jti
    • old: included by default and identical to the one in the access_token body
    • new: could not find a way to include it
  • token_type
    • old: bearer (note the case)
    • new: Bearer

Example

old

{
  "access_token": "...",
  "expires_in": 59,
  "jti": "abcf9112-fa08-4474-a2df-....",
  "scope": "sessions",
  "token_type": "bearer"
}

new

{
  "access_token": "...",
  "expires_in": 59,
  "scope": "sessions",
  "token_type": "Bearer"
}

I feel like I'm missing something obvious but just can't find it in the docs.

To Reproduce
Default sample project

Expected behavior

  1. The jti is included by default, or a way to provide it.
  2. A way to change the case of token_type
@emilburzo emilburzo added the type: bug A general bug label Mar 1, 2023
@emilburzo
Copy link
Author

emilburzo commented Mar 2, 2023

After some more digging around I stumbled upon some similar issues (#1000, #925) and could solve my current issues with the solutions mentioned in the comment(s).

I would vote +1 though on an easier way to hook into this part, it felt like there was a lot of code copied for, essentially, just adding two keys to the additionalParameters map:

builder.additionalParameters(
    mapOf(
        "jti" to decodedAccessToken.id, 
        "token_type" to accessToken.tokenType.value.lowercase() // because the old project had it lowercase
    )
)

@sjohnr
Copy link
Contributor

sjohnr commented Mar 3, 2023

Hi @emilburzo,

I would vote +1 though on an easier way to hook into this part, it felt like there was a lot of code copied for, essentially, just adding two keys to the additionalParameters map:

Can you share what you did specifically here? I'm not clear if you're asking for an easier way than the OAuth2TokenCustomzier which only requires exposing as an @Bean? Or if you chose to go a different route?

@sjohnr sjohnr added status: invalid An issue that we don't feel is valid and removed type: bug A general bug labels Mar 3, 2023
@sjohnr sjohnr self-assigned this Mar 3, 2023
@sjohnr sjohnr added the type: bug A general bug label Mar 3, 2023
@jgrandja jgrandja removed the type: bug A general bug label Mar 5, 2023
@emilburzo
Copy link
Author

emilburzo commented Mar 7, 2023

@sjohnr sure, I'll try

I'm not clear if you're asking for an easier way than the OAuth2TokenCustomizer which only requires exposing as an @bean?

unless I'm missing something, I think that only allows you to customize the jwt object (access_token field in the below example), which is indeed very nice and easy.

what I need though is the ability to customize the actual token endpoint (/oauth2/token) response to make it match the EOL https://github.com/spring-attic/spring-security-oauth, e.g.:

$ curl -s -X POST --url http://localhost:8002/oauth/token --user 'XXXX:YYYY' --header 'Content-Type: application/x-www-form-urlencoded; charset=utf-8' --data 'grant_type=client_credentials' --data 'scope=sessions'

I was able to achieve that using the solution described here: #925 (comment), specifically: providing a custom AuthenticationSuccessHandler, which means copying all of this code:

private void sendAccessTokenResponse(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) authentication;
OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
OAuth2AccessTokenResponse.Builder builder =
OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
.tokenType(accessToken.getTokenType())
.scopes(accessToken.getScopes());
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
}
if (refreshToken != null) {
builder.refreshToken(refreshToken.getTokenValue());
}
if (!CollectionUtils.isEmpty(additionalParameters)) {
builder.additionalParameters(additionalParameters);
}
OAuth2AccessTokenResponse accessTokenResponse = builder.build();
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
}

and changing it like so:

--- a/OAuth2TokenEndpointFilter.java
+++ b/OAuth2TokenEndpointFilter.java
@@ -226,7 +226,7 @@ public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
 
         OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
         OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
-        Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
+        val decodedAccessToken = jwtDecoder.decode(accessToken.tokenValue)
 
         OAuth2AccessTokenResponse.Builder builder =
                 OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
@@ -238,9 +238,12 @@ public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
         if (refreshToken != null) {
             builder.refreshToken(refreshToken.getTokenValue());
         }
-        if (!CollectionUtils.isEmpty(additionalParameters)) {
-            builder.additionalParameters(additionalParameters);
-        }
+        builder.additionalParameters(
+            mapOf(
+                "jti" to decodedAccessToken.id, // must be the same as the access_token jti
+                "token_type" to accessToken.tokenType.value.lowercase() // because the old project had it lowercase
+            )
+        )
         OAuth2AccessTokenResponse accessTokenResponse = builder.build();
         ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
         this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);

(sorry for the syntax, it's kotlin)

which works, but having something as easy as the OAuth2TokenCustomizer would be way nicer in my opinion, as there's less copy/pasting, having to track if the upstream logic ever changes, and so on

I hope that makes it clearer, feel free to ask otherwise

@sjohnr
Copy link
Contributor

sjohnr commented Mar 15, 2023

@emilburzo, thanks for the details. Take a look at gh-925 which may cover your use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

3 participants