Microprofile JWT Auth: Getting Started
Microprofile initiative is really about the cloud. Tracing (based on OpenTracing), configuration (supporting clusters and whatever environment you run in), and security thanks to JWT Auth specification.
The specification provides several API but high level it allows to:
- Secure an application thanks to a JWT, which means you can validate incoming JWT thanks to any implementation of the specification
- Inject any claim (part of the token metadata) into any CDI bean
- Integrate with with javax.security API and typically @RolesAllowed/@PermitAll/@DenyAll API
Secure an application
Once you dropped the implementation into your application, you just need to add on a JAX-RS application the annotation @LoginConfig with the authMethod set to MP-JWT to activate that extension:
@LoginConfig(authMethod = "MP-JWT", realmName = "TCK-MP-JWT")
@ApplicationPath("api")
public class APIApplication extends Application {
}
This requirement will probably be relaxed but today it is needed.
Note that some implementation, like Apache Geronimo one, allow to force the setup even without that annotation. For Geronimo JWT Auth implementation, you will add the following configuration (in Microprofile Config is available or in system properties if not):
geronimo-jwt-auth.filter.active = true
Then, the important part is the configuration of the validation. In the context of this post, I'll skip the vendor specific configuration which allows to do a more advanced setup and I'll stick to the standard configuration.
The validations are "lazy", which means that if you don't have any @RolesAllowed or if you don't access the JWT, then no validation will happen at all. This enables to handle smoothly the public part of the application with no real config and to still secure all the remaning urls of the application which will have some security constraint. These constraints can be either the javax.security API ones or a custom one while you read the JWT to extract the roles or any information.
Here are the configuration you will provide (generally in Microprofile config) to setup JWT validation - Names provides the keys as constants if you need it:
-
org.eclipse.microprofile.authentication.JWT.clockSkew: the date tolerance on the JWT. Typically a JWT comes with an expiration, because synchronizing clocks between computes can be complicated, it is often good to accept some margin on that one (not 1h but a few seconds or minutes).
-
org.eclipse.microprofile.authentication.JWT.issuer(s): the issuers (or issuer if you don't use the plural form), accepted by the server. The JWT contains an issuer and it is generally good to reject the JWT emitted by unknown issuers.
-
org.eclipse.microprofile.authentication.JWT.verifierPublicKey: the default public key to use to verify the incoming JWT (to verify the signature). It is, by default, in PEM format.
This is the minimum set of options. There are also some other constants to be able to call a remote system to get a key set but this is not mandatory.
Side note: implementations will allow you to support multiple keys (using the kid attribute), role/claim mapping and other options, don't hesitate to have a look. Since it uses Microprofile Config, it is easy to tune finely the application without having to package some vendor specific entries.
Inject the token and claims
At that point the implementation is set up (all the filter magic is done under the hood) and you can inject the JWT either directly using the JsonWebToken API or directly a claim with the qualifier @Claim:
@Path("blog")
@RequestScoped
public class SomEndpoint {
@Inject // token.getClaim("...")
private JsonWebToken token;
@Inject
@Claim(standard = Claims.iss)
private String issuer;
@Inject
@Claim("custom_entry")
private String customEntry;
}
As you can see, the claim can be injected either from a raw name (String) or a set of constant for the most common claims (using Claims). What is important to note here, is that the injections must be done in a request scoped bean to avoid to have the value of another request injected. The ClaimValue<?> wrapper, which is in the spec, should allow to go further and inject it in beans of any scope.
Secure the application with roles
Finally the javax.security integration allows to secure the application without having to integrate yourself with the JWT. Concretely you will write this kind of code:
@Path("blog")
@ApplicationScoped
public class SecuredEndpoint {
@GET
@RolesAllowed("Admin")
public Posts findComingPosts() {
// ...
}
}
Plain Old Standard JavaEE code, right? The JWT Auth implementation will automatically grab the JWT, check groups claim (a set/array) and will check it is matching the required role.
As I mentionned earlier, you can map the annotation role name to another claim name if desired, for instance, for Apache Geronimo implementation, you can enter in your configuration this mapping this way:
geronimo.jwt-auth.groups.mapping = \
Admin = admin, sudo, boss,\n\
User = user
The syntax expects a properties value (this is why there is this EOL in the middle) but for technical people or a nice UI to edit the configuration it is not a big deal ;).
Conclusion
At the end of the day, Microprofile JWT Auth specification is not that bad. It has a few coming enhancements (like dopping the requirement of this @LoginConfig, enabling to use injections with more scopes etc...) but it is already uasble and very friendly with a microservice architecture, so no reason to not use it.
From the same author:
In the same category: