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: