Spring Boot Passthrough JWT with RestTemplate

In a microservice environment it is often the case, that calls from a client to a service result in further calls to other services. One possible scenario is a call to a GraphQL service which gathers information from different backend (REST) services and present it as a cohesive data graph.

In this scenario the user is authenticated to the backend services via OAuth2 (e.g., Keycloak or a Spring Boot OAuth2 server) and the GraphQL service should passthrough the authentication header (a JWT bearer) of incoming requests to the backend services. This way the authentication has to be validated only once in the backend services and as “near” as possible to the (REST) resources.

This is not meant as a replacement for service-to-service authentication, but as an addition if you do not use the full OpenID connect standard with a separate identity token to pass on, but still want to serve verifiable user data to your backend service. In contrast, you may use this to pass through any header (including a identity token). This is just a scenario that I faced.

A GraphQL server can be implemented very easy with spring boot using the corresponding starter. The approach described here can be used with any spring boot service including SpringMVC and Spring Data Rest. It is not limited to GraphQL

Each incoming call (e.g. a GraphQL query or mutation) is a http request that will either carry the header “Authorization” with a bearer token, or, if the user is not authenticated, the header will be omitted completely. The naive approach would be to inject the servlet request object into every bean or bean method. But then in every method the header has to be extracted from the request and added to requests on external services (e.g. RestTemplate). This approach is error-prone (e.g. handling the unauthenticated case), it is time-consuming if a change to the handling is necessary, and it is easy to miss some cases. Last but not least, testing becomes harder, since a dummy servlet request object has to be inserted to each method even, if the test is completely independent from authentication. Sure, you may use SpEL to extract the header from the request declaratively and insert the bearer token directly as a string. But still, you have to provide information to each method and passthrough the header to subsequent calls.

This feels so wrong, because passing through authentication tokens is a cross-cutting concern. Hence, we will do it the Spring way via AOP (aspect-oriented programming) to separate the concerns (SoC) instead. I.e., the declaration — how to pass on the bearer token — is moved to the creation of the RestTemplate bean.

@Component
public class RestTemplateConfig {

  /**
   * This specialized version of the Spring RestTemplate supports
   * forwarding the authorization token to the target service for
   * the request. If the current session is not authenticated, no
   * token will be used.
   */
  @Bean
  @RequestScope
  public RestTemplate keycloakRestTemplate(HttpServletRequest inReq) {
    // retrieve the auth header from incoming request
    final String authHeader =
      inReq.getHeader(HttpHeaders.AUTHORIZATION);
    final RestTemplate restTemplate = new RestTemplate();
    // add a token if an incoming auth header exists, only
    if (authHeader != null && !authHeader.isEmpty()) {
      // since the header should be added to each outgoing request,
      // add an interceptor that handles this.
      restTemplate.getInterceptors().add(
        (outReq, bytes, clientHttpReqExec) -> {
          outReq.getHeaders().set(
            HttpHeaders.AUTHORIZATION, authHeader
          );
          return clientHttpReqExec.execute(outReq, bytes);
        });
    }
    return restTemplate;
  }
}

Tests that do not need authentication can now be created without any extra handling and the logic in beans can be implemented completely JWT agnostic.

Exciting 🤓.

Leave a Reply

Your email address will not be published. Required fields are marked *