Adding JWT MtM Authentication

Overview

Atoti Limits includes native support for JSON Web Tokens. We can utilize this support to send MtM requests using the Authorization: Jwt ... header.

note

This support is provided by Atoti Limits, but we will show how it is implemented nonetheless to provide a sample extension mechanism.

Using the default support

To use the native support, specify the following properties in both Atoti Limits and your connected server:

limits:
  autoconfiguration:
    use-jwt-machine-to-machine-auth: true // this tells Atoti Limits and the connected server to use JWT MtM authentication
    service-principal: admin // this tells Atoti Limits and the connected server the principal user designated to invoke MtM request

warning

You must use the kebab-case use-jwt-machine-to-machine-auth and not the camelCase useJwtMachineToMachineAuth or else Spring will not correctly pick up your property.

How it works

As described in Adding Custom Machine-to-Machine (MtM) Authentication, we need to add some custom implementations to our code.

1. Adding a custom ILimitsRestClientProvider

This example is an implementation of ILimitsRestClientProvider, which uses JWT authentication:

@Getter
public class LimitsJwtRestClientProvider implements ILimitsRestClientProvider {

  protected final RestClient limitsRestClient;

  // We inject any services we need into our provider
  public LimitsJwtRestClientProvider( 
      IJwtService jwtService, // this is used to get or generate the JWT
      UserDetailsService userDetailsService, // this is used to get the authorities of the service user
      LimitsConnectionConfigurationProperties limitsConnectionConfigurationProperties 
      // we use this class to pull in properties, but you could use any property extraction mechanism
  ) {

    // we build the client to be used for requests
    this.limitsRestClient =
        buildRestClient(jwtService, userDetailsService, limitsConnectionConfigurationProperties);
  }

  private RestClient buildRestClient(
      IJwtService jwtService,
      UserDetailsService userDetailsService,
      LimitsConnectionConfigurationProperties limitsConnectionConfigurationProperties) {
    String servicePrincipal =
        limitsConnectionConfigurationProperties.getAutoconfiguration().getServicePrincipal();
    if (servicePrincipal == null) {
      throw new LimitsRuntimeException(
          "You must specify the `limits.autoconfiguration.service-principal` property if you are using JWT machine-to-machine authentication!");
    }
    Collection<String> authorities =
        userDetailsService.loadUserByUsername(servicePrincipal).getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toSet());
    String jwt = jwtService.getToken(servicePrincipal, authorities);
    // here is where we set the `Authorization: Jwt ...` header
    return RestClient.builder().defaultHeader(AUTHORIZATION, "Jwt " + jwt).build();
  }
}

2. Adding a custom ILimitsRestClientBuilderProvider

This example is an implementation of ILimitsRestClientBuilderProvider, which uses JWT authentication. It is almost identical to above, except we return a builder instead of a fully constructed client.


@Getter
public class JwtRestClientBuilderProvider implements ILimitsRestClientBuilderProvider {

  protected final RestClient.Builder limitsRestClientBuilder;

  public JwtRestClientBuilderProvider(
      IJwtService jwtService,
      UserDetailsService userDetailsService,
      LimitsAutoconfigurationProperties limitsAutoconfigurationProperties) {
    this.limitsRestClientBuilder =
        buildRestClient(jwtService, userDetailsService, limitsAutoconfigurationProperties);
  }

  private RestClient.Builder buildRestClient(
      IJwtService jwtService,
      UserDetailsService userDetailsService,
      LimitsAutoconfigurationProperties limitsAutoconfigurationProperties) {
    String servicePrincipal = limitsAutoconfigurationProperties.getServicePrincipal();
    if (servicePrincipal == null) {
      throw new LimitsRuntimeException(
          "You must specify the `limits.autoconfiguration.service-principal` property if you are using JWT machine-to-machine authentication!");
    }
    Collection<String> authorities =
        userDetailsService.loadUserByUsername(servicePrincipal).getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toSet());
    String jwt = jwtService.getToken(servicePrincipal, authorities);
    return RestClient.builder().defaultHeader(AUTHORIZATION, "Jwt " + jwt);
  }
}