Skip to content
This repository has been archived by the owner on May 31, 2022. It is now read-only.

Use OAuth2 token_type equal "Bearer" by default everywhere #457

Closed
karwer opened this issue Apr 15, 2015 · 23 comments
Closed

Use OAuth2 token_type equal "Bearer" by default everywhere #457

karwer opened this issue Apr 15, 2015 · 23 comments

Comments

@karwer
Copy link

karwer commented Apr 15, 2015

There is a line
private String tokenType = BEARER_TYPE.toLowerCase();
in DefaultOAuth2AccessToken class. Isn't it inconsistent with RFC stating that the type name should be "Bearer" (https://tools.ietf.org/html/rfc6750#section-6.1.1)?

@dsyer
Copy link
Contributor

dsyer commented Apr 16, 2015

Historical reasons (that's the way we inherited this project). The RFC used to say it was case insensitive, but I can't find that bit now, so it must have changed. I suspect this might be a breaking change for some apps, so we probably ought to wait till 2.1 to change it. The client always sends back the token type it gets from the server though, so in practice it probably often doesn't matter much.

@dsyer dsyer changed the title Why is OAuth2 token_type equal to "bearer" (lower case)? Use OAuth2 token_type equal "Bearer" by default everywhere Apr 16, 2015
@dsyer dsyer added this to the 2.1.0 milestone Apr 16, 2015
@bassman5
Copy link

This causes a problem on https://github.com/spring-cloud/spring-cloud-security/blob/master/src/main/java/org/springframework/cloud/security/oauth2/resource/UserInfoTokenServices.java#L102-L103 that creates a token without setting the type.
This means that the userInfoUri request (and tokenInfoUri) are made with Authentication: "bearer xxx" which fails for Google+.
Changing to "Bearer" fixes this issue. This issue may also be causing spring-attic/spring-cloud-security#59

@dsyer
Copy link
Contributor

dsyer commented Apr 20, 2015

Yeah I know, but it's not something that will get fixed here in 2.0.* (as already explained). The spring cloud bug you mentioned is unrelated, but if you are using spring cloud there are workarounds available (look through github issues for mentions of toke type). The best one is to only send tokens from a rest template that was used to obtain the token (because the token type usually comes with the response from the /token endpoint). If you can't do that then you need to customize the rest template, adding your own "authenticator" (it's a standard extension point in spring oauth).

@bassman5
Copy link

Thanks, I am overriding loadAuthentication in my own UserInfoTokenServices so I can set the tokenType on the token in my version of getMap for now.

@kelsin
Copy link

kelsin commented Apr 30, 2015

Hey guys, just want to point out that the token type field that is returned with an access token IS case insensitive: https://tools.ietf.org/html/rfc6749#section-4.2.2

When USING bearer tokens you need to pass the string "Bearer" (as linked in the RFC by @karwer) but it's improper to ever take the string returned as token_type and pass it along in an Authorization header without some logic involved with the token type.

IMHO not returning the token type with access tokens as lower case implies associations that are not officially there in the specs. We've dealt with many clients trying to use our OAuth server with clients that do not follow the spec, and the behavior right now on spring security (in this regard) seems perfect and on-spec to me.

@karwer
Copy link
Author

karwer commented May 2, 2015

OK, I missed the statement that it is case insensitive. So perhaps there is no point in changing this indeed.

@RogerVanHertsen
Copy link

Case-insensitivity applies for the token type field. However there is no ambiguity on the casing in the request headers it should be 'Bearer'. This should be addressed as multiple APIs respect this strictly.

@dsyer
Copy link
Contributor

dsyer commented May 15, 2015

Spring Cloud now has "Bearer" by default for the user info endpoint (which is the only place anyone ever complained about this), and a configuration setting for changing it if you need to. See spring-attic/spring-cloud-security#63.

@lukeway
Copy link

lukeway commented Aug 21, 2015

We ran into this issue with someone using and old version of WSO2 API Manager (<1.8.0):

https://wso2.org/jira/browse/APIMANAGER-2921

The "bug" was fixed in that software, but we had no control over the service side and could not upgrade. Our hacky solution was to copy DefaultOAuth2AccessToken into our project and make the following changes:

private String tokenType = BEARER_TYPE; // Removed toLowerCase()

and

    public void setTokenType(String tokenType) {
        if(BEARER_TYPE.equalsIgnoreCase(tokenType)) {
            // Force "Bearer" for any case variation
            this.tokenType = BEARER_TYPE;
        } else {
            // Otherwise use as-is
            this.tokenType = tokenType;
        }
    }

Not a great solution, but it worked for us.

@oli99sc
Copy link

oli99sc commented Jan 18, 2016

Just hitting the same problem, with a scala based resource server (that only checks for Bearer).
Regarding comment of @kelsin, rfc6749 describes the data returned by the auth server, https://tools.ietf.org/html/rfc6750#section-2.1 describes Bearer token handling in Request header. Here it does not say anything about case insensitive and uses "Bearer"

@nicodewet
Copy link

You'll run into this when integrating with WSO2 Identity Server 5.0

With spring-security-oauth2 2.0.8.RELEASE I just included this class.


public class WSO2OAuth2RequestAuthenticator extends DefaultOAuth2RequestAuthenticator {
  @Override
  public void authenticate(OAuth2ProtectedResourceDetails resource,
      OAuth2ClientContext clientContext, ClientHttpRequest request) {
    OAuth2AccessToken accessToken = clientContext.getAccessToken();
    String tokenType = OAuth2AccessToken.BEARER_TYPE;
    request.getHeaders().set("Authorization",
        String.format("%s %s", tokenType, accessToken.getValue()));
  }
}

@hrandika
Copy link

@nicodewet do we need this class to be annotated with @configuration ? or just add this to the application. I can't get it working.

@dsyer
Copy link
Contributor

dsyer commented Feb 10, 2016

@hrandika no, there's a setter for it in OAuth2RestTemplate. Create an instance and inject it yourself when you create the template, or if someone else created it for you autowire that one and call its setter.

@nicodewet
Copy link

I have used the WSO2OAuth2RequestAuthenticator class as follows, which is in line with @dsyer's advice. No need for @configuration on WSO2OAuth2RequestAuthenticator nor LoggingRequestInterceptorOAuth2RestTemplateCustomizer.

public class LoggingRequestInterceptorOAuth2RestTemplateCustomizer
    implements UserInfoRestTemplateCustomizer {

  @Override
  public void customize(OAuth2RestTemplate template) {
    ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() {

      private final Logger log = LoggerFactory.getLogger(getClass());

      @Override
      public ClientHttpResponse intercept(HttpRequest request, byte[] body,
          ClientHttpRequestExecution execution) throws IOException {

        ClientHttpResponse response = execution.execute(request, body);

        log(request, body, response);

       return response;
      }

      private void log(HttpRequest request, byte[] body, ClientHttpResponse response)
          throws IOException {
        log.info("HttpRequest METHOD: {}", request.getMethod().toString());
        log.info("HttpRequest HEADERS: {}", request.getHeaders().toString());
        log.info("HttpRequest BODY: {}", body.toString());
        log.info("ClientHttpResponse STATUS CODE: {}", response.getStatusCode().toString());
        log.info("ClientHttpResponse HEADERS: {}", response.getHeaders().toString());
      }
    };
    List<ClientHttpRequestInterceptor> ris = new ArrayList<ClientHttpRequestInterceptor>();
    ris.add(interceptor);
    template.setInterceptors(ris);

    template.setAuthenticator(new WSO2OAuth2RequestAuthenticator());
  }

}

@itzg
Copy link

itzg commented Oct 9, 2016

FYI, I have discovered this issue is preventing use of the BitBucket APIs where this section states an expectation of "Bearer". When changing "B" to "b" via manual curl testing I was able to confirm that was the exact point of failure.

Regarding the RFC 6750 references above, I don't think 4.2.2 is the right one to consider. In the scenario I encountered it is 2.1 that is relevant. It doesn't explicitly state case insensitivity or not, but "Bearer" is stated five times with an uppercase "B".

For posterity, my work around seems to be quite similar to ones above, declaring the following and passing an instance of that to the OAuth2RestTemplate constructor.

import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;

class BearerFixOAuth2ClientContext extends DefaultOAuth2ClientContext {
    @Override
    public void setAccessToken(OAuth2AccessToken accessToken) {
        if ("bearer".equals(accessToken.getTokenType())) {
            final DefaultOAuth2AccessToken fixedToken = new DefaultOAuth2AccessToken(accessToken);
            fixedToken.setTokenType("Bearer");
            super.setAccessToken(fixedToken);
        } else {
            super.setAccessToken(accessToken);
        }
    }
}

Understandably there's going to be OAuth2 servers that fall on either side of the "Bearer" | "bearer" fence, so I would like to at least see an option in DefaultOAuth2AccessToken to normalize to lowercase or not.

@codependent
Copy link

codependent commented Dec 27, 2016

I just run into this issue when integrating with IBM API Connect. In this case, the authorization server returns a "bearer" token_type. When the spec explicitly states that we have to use "Authorization: Bearer XXXX" I don't see the point, in DefaultOAuth2RequestAuthenticator, of using the token_type returned by the authorization server instead of always using "Bearer".

@jgrandja jgrandja modified the milestones: 2.1.1, 2.1.0 Mar 3, 2017
@jgrandja jgrandja modified the milestones: 2.1.1, 2.1.2 May 29, 2017
@michaelgulak
Copy link

Among the other issues listed, this creates an issue with FOS's PHP OAuth2 library (FriendsOfSymfony/oauth2-php#98).

The fact that "token_type" is used as the prefix for the string is quite odd to me -- what is the use case that this enables? "token_type" is effectively a JSON enum (yes, I know those don't actually exist), so there's no reason to pass it through. As @codependent said, it should always simply be set to "Bearer".

@anand7719
Copy link

anand7719 commented Jun 19, 2017

We had a similar situation where the token_type must be Bearer not bearer for backward compatibility reasons. So we created TokenEnchancer and added the following block of code,

`if (OAuth2AccessToken.BEARER_TYPE.equalsIgnoreCase(accessToken.getTokenType())) {

	((DefaultOAuth2AccessToken) accessToken).setTokenType(OAuth2AccessToken.BEARER_TYPE);

}`

@jgrandja jgrandja modified the milestones: 2.1.2, 2.2.0.M1 Jun 29, 2017
@jgrandja jgrandja modified the milestones: 2.2.0.RC1, 2.2.0 Jul 14, 2017
@dcurrytca
Copy link

dcurrytca commented Aug 3, 2017

According to the OAuth 2.0 Bearer Token Usage spec section 2.1 Authorization Request Header field, the format of the credentials field is:

credentials = "Bearer" 1*SP b64token

Note that in the spec, "Bearer" is upper-case in the first character.

@skobow
Copy link

skobow commented Mar 22, 2018

I experienced side effects using BearerFixOAuth2ClientContext as @itzg suggested when optaining new access tokens as no OAuth2ClientContextFilter gets added by the @EnableOAuth2Client annotation when not using the injected client context. I finally extended the JdbcClientTokenServices I am using overriding saveAccessToken and getAccessToken methods. I added the following method for setting correct token type:

@Override
    public OAuth2AccessToken getAccessToken(final OAuth2ProtectedResourceDetails resource, final Authentication authentication) {
        final OAuth2AccessToken accessToken = super.getAccessToken(resource, authentication);
        setRFC675CompliantBearerType(accessToken);
        return accessToken;
    }

    @Override
    public void saveAccessToken(final OAuth2ProtectedResourceDetails resource, final Authentication authentication, final OAuth2AccessToken accessToken) {
        setRFC675CompliantBearerType(accessToken);
        super.saveAccessToken(resource, authentication, accessToken);
    }

private void setRFC675CompliantBearerType(final OAuth2AccessToken accessToken) {
        if (accessToken != null && accessToken instanceof DefaultOAuth2AccessToken) {
            ((DefaultOAuth2AccessToken)accessToken).setTokenType("Bearer");
        }
 }

May be this helps as well...

@mjacksonlgh
Copy link

Spent hours chasing this....also causes issues with user info call on api.battle.net.

@jgrandja
Copy link
Contributor

This will be resolved via #1346

@jgrandja
Copy link
Contributor

jgrandja commented May 7, 2018

Closing in favour of #1346

@jgrandja jgrandja closed this as completed May 7, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

No branches or pull requests