I am using Spring Boot 2.0.6-RELAESE And following configuration for OAuth 2: spring: security: oauth2: client: registration: google: client-id: @google.client-id@ client-secret: @google.client-secret@ facebook: client-id: @facebook.client-id@ client-secret: @facebook.client-secret@ linkedin: client-id: @linkedin.client-id@ client-secret: @linkedin.client-secret@ provider: linkedin scope: r_basicprofile,r_emailaddress client-authentication-method: post authorization-grant-type: authorization_code redirect-uri-template: http://localhost:8080/login/oauth2/code/linkedin client-name: Linkedin

    provider:
      linkedin:
        authorization-uri: https://www.linkedin.com/oauth/v2/authorization
        token-uri: https://www.linkedin.com/oauth/v2/accessToken
        user-info-uri: https://api.linkedin.com/v2/me

Its failing inside NimbusAuthorizationCodeTokenResponseClient at Line 101: tokenResponse = com.nimbusds.oauth2.sdk.TokenResponse.parse(httpRequest.send()); with Parse Exception showing stack trace on console:

com.nimbusds.oauth2.sdk.ParseException: Missing JSON object member with key "token_type" at com.nimbusds.oauth2.sdk.util.JSONObjectUtils.getGeneric(JSONObjectUtils.java:127) ~[oauth2-oidc-sdk-5.64.4.jar:5.64.4] at com.nimbusds.oauth2.sdk.util.JSONObjectUtils.getString(JSONObjectUtils.java:263) ~[oauth2-oidc-sdk-5.64.4.jar:5.64.4] at com.nimbusds.oauth2.sdk.token.BearerAccessToken.parse(BearerAccessToken.java:187) ~[oauth2-oidc-sdk-5.64.4.jar:5.64.4] at com.nimbusds.oauth2.sdk.token.AccessToken.parse(AccessToken.java:273) ~[oauth2-oidc-sdk-5.64.4.jar:5.64.4] at com.nimbusds.oauth2.sdk.token.Tokens.parse(Tokens.java:167) ~[oauth2-oidc-sdk-5.64.4.jar:5.64.4] at com.nimbusds.oauth2.sdk.AccessTokenResponse.parse(AccessTokenResponse.java:199) ~[oauth2-oidc-sdk-5.64.4.jar:5.64.4] at com.nimbusds.oauth2.sdk.AccessTokenResponse.parse(AccessTokenResponse.java:236) ~[oauth2-oidc-sdk-5.64.4.jar:5.64.4] at com.nimbusds.oauth2.sdk.TokenResponse.parse(TokenResponse.java:95) ~[oauth2-oidc-sdk-5.64.4.jar:5.64.4] at org.springframework.security.oauth2.client.endpoint.NimbusAuthorizationCodeTokenResponseClient.getTokenResponse(NimbusAuthorizationCodeTokenResponseClient.java:101) ~[spring-security-oauth2-client-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.security.oauth2.client.endpoint.NimbusAuthorizationCodeTokenResponseClient.getTokenResponse(NimbusAuthorizationCodeTokenResponseClient.java:67) ~[spring-security-oauth2-client-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider.authenticate(OAuth2LoginAuthenticationProvider.java:121) ~[spring-security-oauth2-client-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174) ~[spring-security-core-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:166) ~[spring-security-oauth2-client-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:129) [spring-security-oauth2-client-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.10.RELEASE.jar:5.0.10.RELEASE] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) [spring-security-web-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66) [spring-security-web-5.0.9.RELEASE.jar:5.0.9.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.10.RELEASE.jar:5.0.10.RELEASE] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.9.RELEASE.jar:5.0.9.RELEASE]

The stack trace shows response for accesstoken not contain key "token_type", so its failing. But when I hit access token request with Postman at URL:

https://www.linkedin.com/oauth/v2/accessToken?code=code&client_secret=client_secret&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Flogin%2Foauth2%2Fcode%2Flinkedin&client_id=client_id I am getting the access token in format below: { "access_token": "access_token", "expires_in": 5183999 }

How we can fix it??

Comment From: jgrandja

@ankurpathak We are aware of this issue with LinkedIn and oauth2Login() in Spring Security 5.0.

As an FYI, the token_type parameter is required as per spec.

token_type REQUIRED. The type of the token issued as described in Section 7.1. Value is case insensitive.

In the recent 5.1 release, you are now able to customize the handling of the Token Response by configuring a custom Converter<Map<String, String>, OAuth2AccessTokenResponse>.

Please see the recent reference doc on OAuth2AccessTokenResponseClient.

Also, take a look at this sample configuration and custom Converter<Map<String, String>, OAuth2AccessTokenResponse> implementation. You will notice that CustomAccessTokenResponseConverter defaults OAuth2AccessTokenResponse.tokenType to OAuth2AccessToken.TokenType.BEARER, which will resolve the issue you are having with LinkedIn.

I'm going to close this issue as I feel I have provided you the solution to resolve. If not we can re-open and discuss further.

Comment From: ankurpathak

@jgrandja I got an Idea about this. But I am doing multiple provider Google, Facebook and Linkedin. How I will make sure it will kick in for only Linkedin and notfor other providers facbook and google which are just working fine.

Comment From: jgrandja

@ankurpathak I put together a sample that has LinkedIn, Google and local UAA.

See the linkedin branch

Comment From: ankurpathak

@jgrandja Linkedin now works like a champ. Thanks.

Comment From: b2bdev247

@jaffadog , Hey could you help me with this. My configuration looks like this. I am using springboot 2.4.4

Property configuration


security:
    oauth2:
      client:
        registration:
          google:
            client-id: xxx
            client-secret: xxx
            redirect-uri: "{baseUrl}/oauth2/callback/{registrationId}"
            scope:
            - email
            - profile
          facebook:
            client-id: xxx
            client-secret: xxx
            redirect-uri: "{baseUrl}/oauth2/callback/{registrationId}"
            scope:
            - email
            - public_profile
          linkedin:
            client-id: xxx
            client-secret: xxx
            client-authentication-method: post
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/oauth2/code/{registrationId}"
            scope:
            - r_emailaddress
            - r_liteprofile
            client-name: Linkedin
        provider:
          linkedin:
            authorization-uri: https://www.linkedin.com/oauth/v2/authorization
            token-uri: https://www.linkedin.com/oauth/v2/accessToken
            user-info-uri: https://api.linkedin.com/v1/people/~?format=json
            user-name-attribute: id

And my security configuration

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
      .cors()
        .and()
      .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
      .csrf()
        .disable()
      .formLogin()
        .disable()
      .httpBasic()
        .disable()
      .exceptionHandling()
        .authenticationEntryPoint(new RestAuthenticationEntryPoint())
        .and() 
            .authorizeRequests()
        .antMatchers("/", "/error", "/favicon.ico", "/**/*.png", "/**/*.gif", "/**/*.svg", "/**/*.jpg",
                "/**/*.html", "/**/*.css", "/**/*.js")
          .permitAll()
        .antMatchers("/authenticate", "/oauth2/**").permitAll()
        .anyRequest().authenticated()
          .and()
        .oauth2Login()
          .authorizationEndpoint()
            .baseUri("/oauth2/authorize")
            .and()
          .redirectionEndpoint()
            .baseUri("/oauth2/code/*")
            .and()
          .userInfoEndpoint()
            .userService(customOAuth2UserService)
            .and()
          .successHandler(successHandler)
          .failureHandler(failureHandler);

    http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
  }

The same code and configuration work for google and Facebook. In the LinkedIn case, I am redirected to the login page and then it redirects me to http://localhost:8080/oauth2/code/linkedin?code=AQSVbmYf4 then after my flow is not moving forward.

Comment From: sebogado

@b2bdev247 have you found the solution to that?

Comment From: Kehrlann

Stumbled upon this. Here's Joe's sample, crafted specifically for LinkedIn, in "2023 + Spring-Security-6 + Java 17" style, condensed in a single method:

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(
                        httpReq -> httpReq.anyRequest().authenticated()
                )
                .oauth2Login(oauth2 ->
                        oauth2.tokenEndpoint(
                                token -> token.accessTokenResponseClient(linkedinTokenResponseClient())
                        )
                )
                .build();
    }

    private static DefaultAuthorizationCodeTokenResponseClient linkedinTokenResponseClient() {
        var defaultMapConverter = new DefaultMapOAuth2AccessTokenResponseConverter();
        Converter<Map<String, Object>, OAuth2AccessTokenResponse> linkedinMapConverter = tokenResponse -> {
            var withTokenType = new HashMap<>(tokenResponse);
            withTokenType.put(OAuth2ParameterNames.TOKEN_TYPE, OAuth2AccessToken.TokenType.BEARER.getValue());
            return defaultMapConverter.convert(withTokenType);
        };

        var httpConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
        httpConverter.setAccessTokenResponseConverter(linkedinMapConverter);

        var restOperations = new RestTemplate(List.of(new FormHttpMessageConverter(), httpConverter));
        restOperations.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
        var client = new DefaultAuthorizationCodeTokenResponseClient();
        client.setRestOperations(restOperations);
        return client;
    }
}

Comment From: kashyapjain

Stumbled upon this. Here's Joe's sample, crafted specifically for LinkedIn, in "2023 + Spring-Security-6 + Java 17" style, condensed in a single method:

```java @Configuration public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
            .authorizeHttpRequests(
                    httpReq -> httpReq.anyRequest().authenticated()
            )
            .oauth2Login(oauth2 ->
                    oauth2.tokenEndpoint(
                            token -> token.accessTokenResponseClient(linkedinTokenResponseClient())
                    )
            )
            .build();
}

private static DefaultAuthorizationCodeTokenResponseClient linkedinTokenResponseClient() {
    var defaultMapConverter = new DefaultMapOAuth2AccessTokenResponseConverter();
    Converter<Map<String, Object>, OAuth2AccessTokenResponse> linkedinMapConverter = tokenResponse -> {
        var withTokenType = new HashMap<>(tokenResponse);
        withTokenType.put(OAuth2ParameterNames.TOKEN_TYPE, OAuth2AccessToken.TokenType.BEARER.getValue());
        return defaultMapConverter.convert(withTokenType);
    };

    var httpConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
    httpConverter.setAccessTokenResponseConverter(linkedinMapConverter);

    var restOperations = new RestTemplate(List.of(new FormHttpMessageConverter(), httpConverter));
    restOperations.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
    var client = new DefaultAuthorizationCodeTokenResponseClient();
    client.setRestOperations(restOperations);
    return client;
}

} ```

Thanks it works

Comment From: Kehrlann

Hey @saeedmirzapour !

I don't have a sample project at hand to test this out, but it seems LinkedIn wants the token request to have client_id and client_secret in the body of the POST request, not in an Authorization: Basic ... header (source: LinkedIn docs)

This can be achieved by updating your linkedin client registration, and setting it to client_secret_post. For example in application.yaml (some of the properties may be missing in my example):

spring:
  security:
    oauth2:
      client:
        registration:
          linkedin:
            client-id: ...
            client-secret: ...
            scope:
              - ...
            client-authentication-method: client_secret_post
        provider:
          linkedin:
            authorization-uri: ... 
            token-uri: ...

Comment From: saeedmirzapour

client_secret_post

Thank you @Kehrlann, But it didn't help. since it may not be related to the current issue I've opened a new one:

14576