Article originally posted on my personal website under Using JWT in Java
A useful feature of a web application is the possibility to authorize a client to access certain features of the app. Once authentication happens, it is important to also check that a client has access to the requested feature. An easy to use method is provided by a JSON Web Token. These can be easily generated, can hold the data needed for authorization, and most importantly, are secure. Let us look at how a JWT is generated by the server and later verified.
What is a JWT
First, let us look at the structure of the JWT. At first glance, it may not look too interesting, but the structure of this randomly-seeming string holds all the needed information. As the name suggests, it consisted of a JSON String, but it is Base64-encoded. The first part (in red), stores the algorithm used, the second part the claims (needed for authorization), and the last part the signature.
This signature guarantees that a forged JWT can’t be provided and that this existing one can’t be altered to add new claims or extends its lifetime. This is because the token is signed using a key that is known only by the server. I won’t go into more details on the structure or on how the signature is calculated and verified (there are enough materials on the internet), but the bottom line is that you can’t alter them and have it later accepted by the server.
Generating a JWT
Depending on the programming language you use, there are many libraries out there that do most of the needed work. In this case, I will be using the library provided by auth0 for Java. It is free, open-source, and easy to use.
Let’s assume that you are working on a server that needs to provide an access token to a third-party application. An administrator can provide the access token and specify exactly to what endpoints or features that application has access to. For example, you want to let a viewer app only read data from the User’s endpoint, while another, a more advanced one, can also edit those users. Two different access tokens can be provided (one for each client) that hold the exact access rights.
It will still take some work on the server-side to validate the access right stored in the token, but it is easy to generate as many tokens as needed and with dynamically added rights. If you want to elevate the rights for an app, just provide it with a new token. Furthermore, JWTs can also have an expiration date, in case you want to provide access for only a few days.
So, here is how we do it. We first select the algorithm we want to use for signing. For simplicity I will be using HMAC256 which uses a secret password, however you can use a public-private key pair with an RSA algorithm if you so desire. This second approach is useful if the client also needs to validate the JWT to make sure it comes from your server.
public class MyServer {
public String generateJwt(String userId, List<String> accessAreas) {
Algorithm algorithm = Algorithm.HMAC256("mySecret");
JWTCreator.Builder jwtBuilder = JWT.create()
.withIssuer("myServer")
.withClaim("accessGrantedBy", userId)
.withClaim("accessArea", accessAreas);
return jwtBuilder.sign(algorithm);
}
}
Next, we create a builder for the JWT (the library uses the Builder pattern) and add an issuer (optional) that is the server who granted the access, and claims, which is the list of access areas that we will allow to use. We can provide as many claims as needed. At the end, we sign the JWT with the chosen algorithm, and the String value is returned.
Verifying a JWT
Once a JWT was provided to the client, it can be used for authorization to the system. Usually, this is provided in the Authorization header or, where applicable, it is stored as a cookie or in the session. When the server receives a JWT, it should first validate the token. This can also be done with ease thanks to the auth0 library.
public DecodedJWT decodeAndValidateToken(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256("mySecret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("myServer")
.build();
return verifier.verify(token);
} catch (JWTVerificationException exception){
return null;
}
}
Just like before, we initialize the algorithm that will be used. It is important that the same algorithm and secret are used, otherwise, validation will fail. Next, we create a JWTVerifier and pass the algorithm and (optionally) the issuer. When calling verify() on the token, it will be decoded. If the token is invalid, the signature is wrong, it has expired or it has been altered in any way, the library will throw an exception.
Now, if the JWT is valid, we can verify that the client truly has access to the requested feature. To do this, we can verify the claims stored in the token and validate that it is present.
public boolean hasAccess(DecodedJWT jwt, String areaToAccess) {
if (jwt == null) return false;
return jwt.getClaim("accessArea")
.asList(String.class)
.contains(areaToAccess);
}
For simplicity, we can combine the verify, decode and access check in one method that can be called when the endpoint or feature is called. Below is the full class that does this. Further enhancements can be done and in my next article on this topic, I will show how to revoke a JWT so that you can have a full login/logout flow.
public class MyServer {
public String generateJwt(String userId, List<String> accessAreas) {
Algorithm algorithm = Algorithm.HMAC256("mySecret");
JWTCreator.Builder jwtBuilder = JWT.create()
.withIssuer("myServer")
.withClaim("accessGrantedBy", userId)
.withClaim("accessArea", accessAreas);
return jwtBuilder.sign(algorithm);
}
public DecodedJWT decodeAndValidateToken(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256("mySecret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("myServer")
.build();
return verifier.verify(token);
} catch (JWTVerificationException exception){
return null;
}
}
public boolean hasAccess(DecodedJWT jwt, String areaToAccess) {
if (jwt == null) return false;
return jwt.getClaim("accessArea")
.asList(String.class)
.contains(areaToAccess);
}
public boolean hasAccess(String token, String areaToAccess) {
DecodedJWT jwt = decodeAndValidateToken(token);
return hasAccess(jwt, areaToAccess);
}
Article originally posted on my personal website under Using JWT in Java
Comments (0)