Search code examples
springspring-bootoauth-2.0linkedin-api

Integrating LinkedinAPI with Spring Security OAuth


I am in a very weird problem, most probably caused by linkedin API. I have configured linkedin as a provider, as normally as anyone can. PFA Snippets.

Problem is very weird: For me to get user's information from endpoint, I need openid in scope. Now, even though scope is openid, linkedin returns an opaque token and doesn't provide JWKs URI.

By looking openid in scope, spring's OAuth2LoginAuthenticationProvider rejects it and asks OidcAuthorizationCodeAuthenticationProvider (See line where it calls createOidcToken which internally looks for JWKs URI) Problem is, Linkedin doesn't return a JWT in the first place.

Important Note: I have tried keeping openid scope initially and removing in debug point so that OAuth2LoginAuthenticationProvider accepts it, and it works.

I am trying to create my CustomAuthenticationProvider, but Spring is failing to inject dependencies.

FILES*

application.yml

 spring:
  security:
    oauth2:
      client:
        registration:
          linkedin:
            provider: linkedin
            client-id: ${LINKEDIN_CLIENT_ID}
            client-secret: ${LINKEDIN_CLIENT_SECRET}
            client-authentication-method: client_secret_post
            authorization-grant-type: 'authorization_code'
            redirect-uri: '{baseUrl}/login/oauth2/code/linkedin'
            scope:
              - profile
              - email
              - openid
        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/userinfo
            user-name-attribute: sub

Config File

    @Bean
    @Order(2)
    public  SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers("/login", "/resources/**", "/logout")
                        .permitAll()
                        .anyRequest().authenticated()
                )
                .oauth2Login(oauthLoginConfig -> oauthLoginConfig
                        .tokenEndpoint(tokenEndpointConfig -> {
                            tokenEndpointConfig.accessTokenResponseClient(linkedinTokenResponseClient());
                        }));
        return httpSecurity.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;
    }

CustomAuthenticationProvider
Copied as it is from here Except, deleted line 98 to 105.


Solution

  • Actually linkedin DOES SEND id_token (open id token) as JWT.

    I was getting error because once it parses JWT, next thing it does is verifies Nonce which LinkedIn does not support. That's where error was coming from.

    So to prevent that, remove Nonce from request and spring will understand that you don't want nonce verification.

    
    public  SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {
            httpSecurity
                    .authorizeHttpRequests((authorize) -> authorize
                            .requestMatchers("/login", "/resources/**", "/logout")
                            .permitAll()
                            .anyRequest().authenticated()
                    )
                    .oauth2Login(oauthLoginConfig -> oauthLoginConfig
                            .authorizationEndpoint((authorizationEndpointConfig ->
                                    authorizationEndpointConfig.authorizationRequestResolver(
                                            requestResolver(this.clientRegistrationRepository) // See this.
                                    ))
                            )
                            .tokenEndpoint(tokenEndpointConfig -> tokenEndpointConfig
                                    .accessTokenResponseClient(linkedinTokenResponseClient())
                            )
                    );
            return httpSecurity.build();
        }
    
    private static DefaultOAuth2AuthorizationRequestResolver requestResolver
                (ClientRegistrationRepository clientRegistrationRepository) {
            DefaultOAuth2AuthorizationRequestResolver requestResolver =
                    new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
                            "/oauth2/authorization");
            requestResolver.setAuthorizationRequestCustomizer(c ->
                    c.attributes(stringObjectMap -> stringObjectMap.remove(OidcParameterNames.NONCE))
                            .parameters(params -> params.remove(OidcParameterNames.NONCE))
            );
    
            return requestResolver;
        }