DEV Community

Cover image for Authentication between microservices - Part II
Suraj
Suraj

Posted on

Authentication between microservices - Part II

This is a continuation of Authentication between microservices - Part I.

Please read part I to know the idea behind this implementation.

We will use spring-boot to make two services and see how authentication can be implemented.

Let's build caller service A

  • First, configure the keys in application.yml that will be used to create JWT token
service:
  jwtAlgorithmKey: sampleJwtAlgorithmKey #will be used as key in JWT Algo
  serviceBSecretKey: serviceB-SecretKey-To-Be-Provided-By-ServiceA #this is the key that called service (`service B`) will compare on decoding token

spring:
  application:
    name: service-a #application-name
  • Make a config class which will generate jwt tokens at the time of service startup using the values provided in application.yml file
@Configuration
@ConfigurationProperties(prefix = "service")
public class ServiceAuthConfig {

  @Setter @Getter private String jwtAlgorithmKey;

  @Setter @Getter private String serviceBSecretKey;

  @Value("${spring.application.name}")
  private String applicationName;

  @Getter private String serviceBAuthToken;

  /*
    Generate auth token which will be passed in header when calling 
    API of service B
   */
  @PostConstruct
  public void load() {
    serviceBAuthToken = createJwtToken(serviceBSecretKey);
  }

  /*
   Create JWT Token with the algorithm key (common for both the called and caller service)
   Service Secret key is to be generate by caller service and then shared
   and stored in called service yml
  */
  private String createJwtToken(String serviceSecretKey) {
    return JWT.create()
        .withIssuer(applicationName)
        .withSubject(serviceSecretKey)
        .sign(Algorithm.HMAC512(jwtAlgorithmKey));
  }
}
  • Make a feign client to invoke service B's apis
@FeignClient(name = "serviceBClient", url = "http://127.0.0.1:8081/service-b")
public interface ServiceBFeignClient {

  @GetMapping(value = "/get-greetings")
  @ApiOperation("Get Greetings from service B")
  String getGreetings(@RequestHeader("SERVICE-AUTH-TOKEN") String serviceAuthToken);
}
  • Make an api which will call service B's getGreetings API
@RestController
@RequestMapping(value = "/service-a")
@Slf4j
public class ServiceAController {

  @Autowired private ServiceAuthConfig serviceAuthConfig;

  @Autowired private ServiceBFeignClient serviceBFeignClient;

  @GetMapping(value = "/get-greeting-from-service-b")
  public ResponseEntity getGreetingFromServiceB() {
    try {
      String serviceBGreetingResponse =
          serviceBFeignClient.getGreetings(serviceAuthConfig.getServiceBAuthToken()); // passing jwt auth token for service B
      return ResponseEntity.ok(serviceBGreetingResponse);
    } catch (FeignException exception) { 
      /*
        Extract the error thrown from service B 
        and if the error is `INVALID_TOKEN`, then return 401
       */
      String error = new String(exception.responseBody().get().array());
      log.error("Error {}", error);
      if ("INVALID_TOKEN".equals(error)) {
        return ResponseEntity.status(401).body(error);
      }
      throw exception;
    }
  }
}

Now, Let's create a called service B

  • Configure the jwt algo key and the secret key provided by caller service A in application.yml
service:
  jwtAlgorithmKey: sampleJwtAlgorithmKey
  registeredSecretKeys:
    - serviceB-SecretKey-To-Be-Provided-By-ServiceA

spring:
  application:
    name: service-b
  • Expose an api which will generate a greeting
@RestController
@RequestMapping(value = "/service-b")
public class ServiceBController {

  @GetMapping(value = "/get-greetings")
  public ResponseEntity sayGreeting() {
    return ResponseEntity.ok("Hello There from Service B!!!");
  }
}
  • Create a config component to auto read the application.yml properties
@Configuration
@ConfigurationProperties(prefix = "service")
@Slf4j
@Setter
@Getter
public class ServiceAuthConfig {

  private String jwtAlgorithmKey;

  private List<String> registeredSecretKeys;
}
  • Finally, create a ServiceAuthFilter which will read the token from SERVICE-AUTH-TOKEN header, decode and validate it against the keys stored in config
@Component
@Slf4j
public class ServiceAuthFilter implements Filter {

  @Autowired private ServiceAuthConfig serviceAuthConfig;

  @Override
  public void doFilter(
      ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
      throws IOException, ServletException {

    HttpServletRequest httpServletRequest = ((HttpServletRequest) servletRequest);
    String serviceAuthToken = httpServletRequest.getHeader("SERVICE-AUTH-TOKEN");
    boolean isTokenValid = validateJwtToken(serviceAuthToken);
    if (!isTokenValid) {
      HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
      httpServletResponse.setStatus(401);
      httpServletResponse.getWriter().write("INVALID_TOKEN");
    } else {
      filterChain.doFilter(servletRequest, servletResponse);
    }
  }

  /*
   Validate the token by comparing the secret key in it against list 
    of secret keys registered in service B
  */
  private boolean validateJwtToken(String jwtToken) {
    if (StringUtils.isEmpty(jwtToken)) {
      return false;
    }
    DecodedJWT decodedJWT =
        JWT.require(Algorithm.HMAC512(serviceAuthConfig.getJwtAlgorithmKey()))
            .build()
            .verify(jwtToken);
    log.info("Request Initiated from {}", decodedJWT.getIssuer());
    return serviceAuthConfig.getRegisteredSecretKeys().contains(decodedJWT.getSubject());
  }
}

Now, how to validate what has been built?

  • Build and start both, service A and service B.

  • Invoke get-greeting-from-service-b api from caller service A and see the output by keeping the secret keys same in both services and then by changing it in one of them.

More on implementation 👇

GitHub logo s2agrahari / authentication-between-microservices

authentication-between-microservices

authentication-between-microservices

authentication-between-microservices

🍻 If you enjoyed this story, please click the ❤️ button and share it to help others find it! Feel free to leave a comment below.

Discussion (0)