This page details JWT.

Runtime Dependency

<dependency>
  <groupId>io.yupiik.fusion</groupId>
  <artifactId>fusion-jwt</artifactId>
  <version>${fusion.version}</version>
</dependency>

Configuration

The configuration is a JwtValidatorConfiguration (you have to integrate it in your application configuration or reuse the jwt named one you can inject). Here are the subkeys:

Key Default Description

jwt.key

required

The public/private key used to validate.

jwt.algo

RS256

The signature algorithm.

jwt.issuer

-

The expected issuer.

jwt.tolerance

30s

Clock tolerance in seconds.

jwt.jtiRequired

true

Whether the JWT ID is required.

jwt.expRequired

true

Whether the expiration is required.

jwt.iatRequired

false

Whether the issued-at is required.

jwt.nbfRequired

false

Whether the not-before is required.

jwt.keys

-

A JWKS list of keys (kid/kty/alg/use/n/e/x5c/crv/x/y).

The signer side uses a JwtSignerConfiguration with jwt-signer.* keys (key required, algorithm RS256, issuer required, kid k001, expRequired true, expValidity, iatRequired false, nbfRequired false).

Usage

Entry point is to inject a JwtValidatorFactory (through constructor or @Injection on a field) and create an instance of validator passing a JwtValidatorConfiguration which sets the JWT algorithm, issuer, key etc...

@ApplicationScoped
public class MyService {
  private final Function<String, Jwt> jwtValidator;

  protected MyService() {
    // no-op, for application scoped proxy
  }

  public MyService(final JwtValidatorFactory jwtValidatorFactory, final MyAppConfig config) { (1)
    this.jwtValidator = jwtValidatorFactory.newValidator(config.jwt()); (2)
  }

  public Optional<Jwt> findJwt(final Request request) { (3)
    final var jwt = request.header("authorization");
    if (jwt == null || !jwt.startsWith("Bearer ")) {
        return empty();
    }
    try {
      return jwtValidator.apply(jwt.substring("Bearer ".length()));
    } catch (final RuntimeException re) {
      // depending your code you can log the error or not but generally good to log it
      return empty();
    }
  }
}
  1. Inject the validator factory and the JWT validator configuration (key, algorithm, ...),
  2. Create the runtime validator (once for the app),
  3. If the JWT header is present extract it (or fail with an exception if invalid).

Integration with web layer

For JSON-RPC module, the easiest integration is to observe io.yupiik.fusion.jsonrpc.event.BeforeRequest event and plug your validation logic there (claim checks for example):

// jwtValidator as in MyService
public void onJsonRpcJwt(@OnEvent final BeforeRequest event) {
  final var jwt = readJwtFromHeaderValue(event.request().header("authorization")); // (1)
  final var validated = jwtValidator.apply(jwt); (2)
  if (!validated
        .map(ok -> ok.claim("roles", List.class).contains("admin")) (3)
        .orElse(true)) {
 (4)
    final var failed = new CompletableFuture<Response>();
    failed.completeExceptionally(new JsonRpcException(...)); // set your code, message, data
    event.promises().add(failed);
  }
}
  1. Like in MyService (previous snippet), extract the jwt from the header checking/dropping Bearer prefix,
  2. Validate the JWT,
  3. Check roles claim contains admin ,
  4. Complete the request as failed (all requests if bulk is used).
TIP

if you don't want to redefine the configuration nor the validator, you can inject io.yupiik.fusion.jwt.bean.DefaultJwtValidator directly.