The JWT module intend to parse and validate a JWT just using fusion-json dependency and the JVM.

Most of the time you will use an API gateway to do that but for advanced business validations or lighter architectures, it can make sense to do it in the application.

Runtime Dependency

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

Configuration

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

  • jwt-signer.algorithm (JWT_SIGNER_ALGORITHM) (default: "RS256"): Default JWT alg value if no keys is set (mainly useful for Hmac case).

  • jwt-signer.expRequired (JWT_SIGNER_EXPREQUIRED) (default: true): Is exp (expiry) required.

  • jwt-signer.expValidity (JWT_SIGNER_EXPVALIDITY) (default: 0): Is exp is required the validity used in milliseconds.

  • jwt-signer.iatRequired (JWT_SIGNER_IATREQUIRED) (default: false): Is iat (issued at) required.

  • jwt-signer.issuer (JWT_SIGNER_ISSUER)*: JWT issuer.

  • jwt-signer.key (JWT_SIGNER_KEY)*: Private key.

  • jwt-signer.kid (JWT_SIGNER_KID) (default: "k001"): KID header kid.

  • jwt-signer.nbfRequired (JWT_SIGNER_NBFREQUIRED) (default: false): Is nbf (not before) required.

  • jwt.algo (JWT_ALGO) (default: "RS256"): Default JWT alg value if no keys is set (mainly useful for Hmac case).

  • jwt.expRequired (JWT_EXPREQUIRED) (default: true): Is exp (expiry) validation required of can it be skipped if claim is missing.

  • jwt.iatRequired (JWT_IATREQUIRED) (default: false): Is iat (issued at) validation required of can it be skipped if claim is missing.

  • jwt.issuer (JWT_ISSUER): JWT issuer, validation is ignored if null.

  • jwt.jtiRequired (JWT_JTIREQUIRED) (default: true): Is jta (expiry) validation required of can it be skipped if claim is missing.

  • jwt.key (JWT_KEY)*: Default public key to use to validate the incoming JWT if no keys is set else kid is matched against the keys set (mainly useful for Hmac case which can't be in jwk_uri).

  • jwt.keys.$index.alg (JWT_KEYS_INDEX_ALG): -.

  • jwt.keys.$index.crv (JWT_KEYS_INDEX_CRV): -.

  • jwt.keys.$index.e (JWT_KEYS_INDEX_E): -.

  • jwt.keys.$index.kid (JWT_KEYS_INDEX_KID): -.

  • jwt.keys.$index.kty (JWT_KEYS_INDEX_KTY): -.

  • jwt.keys.$index.n (JWT_KEYS_INDEX_N): -.

  • jwt.keys.$index.use (JWT_KEYS_INDEX_USE): -.

  • jwt.keys.$index.x5c (JWT_KEYS_INDEX_X5C): -.

  • jwt.keys.$index.x (JWT_KEYS_INDEX_X): -.

  • jwt.keys.$index.y (JWT_KEYS_INDEX_Y): -.

  • jwt.nbfRequired (JWT_NBFREQUIRED) (default: false): Is nbf (not before) validation required of can it be skipped if claim is missing.

  • jwt.tolerance (JWT_TOLERANCE) (default: 30): Tolerance for date validation (in seconds).

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.