5

I'm a bit confused regarding whether I should be accessing my Spring Boot Resource Server via an access_token or an id_token.

First, let me quickly explain my setup:

  • Spring Boot app as an OAuth 2.0 Resource Server. This is configured as described in the Spring docs: Minimal Configuration for JWTs This app provides secured @Controllers that will provide data for a JavaScript SPA (eg. React)
  • Google's OAuth 2.0 AP / OpenID Connect already configured (Credentials, Client Id, Client Secret)
  • A JavaScript SPA app (eg. React) that logs the user into Google and makes requests to the Spring Boot Resource Server for secured data. These requests include the Authorization header (with Bearer token obtained from Google) for the logged in user.
  • For development purposes, I'm also using Postman to make requests to the Spring Boot Resource Server

I can easily configure Postman to get a token from Google. This token response from Google includes values for access_token, id_token, scope, expries_in and token_type.

However, my requests to the Resource Server are denied when Postman tries to use the value from retrieved token's access_token field as the Bearer in the Authorization header

The only way I'm able to successfully access the secured @Controllers is by using the id_token as the Bearer in the Authorization header.

Is it expected that I should use the id_token as the Bearer in the Authorization header? Or is it expected that I should use the access_token?

Some additional relevant info:

  • The value of the id_token is a JWT token. The value of the access_token is not a JWT token. I know this because I can decode the id_token on jwt.io but it is unable to decode the value of the access_token. Further, the Spring Boot Resource Server fails with the following when I send the access_token as the Bearer in the Authorization header:

An error occurred while attempting to decode the Jwt: Invalid unsecured/JWS/JWE header: Invalid JSON: Unexpected token ɭ� at position 2.

You should not use an identity token to authorize access to an API. To access an API, you should be using OAuth’s access tokens, which are intended only for the protected resource (API) and come with scoping built-in.

  • Looking at at the spring-security-samples for using OAuth2 Resource Server, I see the value of there hard-coded access_token (for testing purposes) is indeed a valid JWT. As opposed to the access_token returned from Google which is not a JWT.

In summary:

  • I can access my Spring Boot Resource Server using the value of the id_token obtained from Google. The value of the access_token is not a JWT and fails to parse by Spring Boot.
  • Is there something wrong with my understanding, my configuration or what? Does Google's OpenId Connect behave differently regarding how the access_token works?

Happy to clarify or add more info if needed. Thanks for your consideration and your patience!

Justin
  • 6,031
  • 11
  • 48
  • 82
  • I would like to build a sample to verify what you're seeing and determine if it's expected and, for example, whether you need to configure it differently. Can you provide the application yml or properties you're using on the resource server (with client id/secret omitted or scrubbed)? – Steve Riesenberg Feb 24 '22 at 17:17
  • Oh, sorry. You provided it on the [other question](https://stackoverflow.com/questions/71245108/how-to-configure-spring-boot-oauth2-resource-server-to-work-with-googles-mu?noredirect=1#comment125950135_71245108). Since it's just a resource server, it's simpler. Nevermind! – Steve Riesenberg Feb 24 '22 at 17:23

1 Answers1

3

The blog post you mentioned is correct in my view, and I believe the OpenID Connect 1.0 spec does not intend for an id_token to be used for access purposes.

Like you, I expected that using Google as an Authorization Server would work out of the box, because Spring Security works with Google as a common OAuth2 provider for providing social login. However, this is not the case, and I believe it is not really intended, because Google is not really your authorization server. For example, I don't believe you can configure Google to work with scopes/permissions/authorities of your domain-specific application. This is different from something like Okta, where there are many options for configuring things in your own tenant.

I would actually recommend checking out Spring Authorization Server, and configuring Google as a federated identity provider. I'm working on a sample for this currently and it will be published within the next week or so (see this branch).

Having said that, if you're still interested in a simple use case where Google access tokens are used for authenticating with your resource server, you would need to provide your own opaque token introspector that uses Google's tokeninfo endpoint. It doesn't match what Spring Security expects, so it's a bit involved.

@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .authorizeRequests((authorizeRequests) -> authorizeRequests
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
        // @formatter:on

        return http.build();
    }

    @Bean
    public OpaqueTokenIntrospector introspector() {
        return new GoogleTokenIntrospector("https://oauth2.googleapis.com/tokeninfo");
    }

}
public final class GoogleTokenIntrospector implements OpaqueTokenIntrospector {
    private final RestTemplate restTemplate = new RestTemplate();
    private final String introspectionUri;

    public GoogleTokenIntrospector(String introspectionUri) {
        this.introspectionUri = introspectionUri;
    }

    @Override
    public OAuth2AuthenticatedPrincipal introspect(String token) {
        RequestEntity<?> requestEntity = buildRequest(token);
        try {
            ResponseEntity<Map<String, Object>> responseEntity = this.restTemplate.exchange(requestEntity, new ParameterizedTypeReference<>() {});
            // TODO: Create and return OAuth2IntrospectionAuthenticatedPrincipal based on response...
        } catch (Exception ex) {
            throw new BadOpaqueTokenException(ex.getMessage(), ex);
        }
    }

    private RequestEntity<?> buildRequest(String token) {
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("access_token", token);

        return new RequestEntity<>(body, headers, HttpMethod.POST, URI.create(introspectionUri));
    }
}
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://accounts.google.com
          jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs
Steve Riesenberg
  • 4,271
  • 1
  • 4
  • 26