JWT validation
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 JWTalg
value if nokeys
is set (mainly useful for Hmac case). -
jwt-signer.expRequired
(JWT_SIGNER_EXPREQUIRED
) (default:true
): Isexp
(expiry) required. -
jwt-signer.expValidity
(JWT_SIGNER_EXPVALIDITY
) (default:0
): Isexp
is required the validity used in milliseconds. -
jwt-signer.iatRequired
(JWT_SIGNER_IATREQUIRED
) (default:false
): Isiat
(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
): Isnbf
(not before) required. -
jwt.algo
(JWT_ALGO
) (default:"RS256"
): Default JWTalg
value if nokeys
is set (mainly useful for Hmac case). -
jwt.expRequired
(JWT_EXPREQUIRED
) (default:true
): Isexp
(expiry) validation required of can it be skipped if claim is missing. -
jwt.iatRequired
(JWT_IATREQUIRED
) (default:false
): Isiat
(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
): Isjta
(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 nokeys
is set elsekid
is matched against thekeys
set (mainly useful for Hmac case which can't be injwk_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
): Isnbf
(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();
}
}
}
- Inject the validator factory and the JWT validator configuration (key, algorithm, ...),
- Create the runtime validator (once for the app),
- 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);
}
}
-
Like in
MyService
(previous snippet), extract the jwt from the header checking/droppingBearer
prefix, - Validate the JWT,
-
Check
roles
claim containsadmin
, - 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 |