Spring REST API authentication using JSON Web Tokens
In the last post I showed how to implement form-based authentication with Spring Security in the context of a Single Page Application. Plugging our own implementation in some points of the Spring Security model allowed to have custom response codes to the login and logout endpoints while keeping the default logic: session creation, response with the Set-Cookie header containing the id of the session, etc.
However, this approach is not suitable for REST APIs. The fact that the server keeps a session with information about the user is in conflict with the REST stateless principle:
Respecting this principle allows us to scale a system easily (you don’t need to share sessions among the nodes) and simplifies the monitoring and recovering processes, as stated by Roy Fielding in his dissertation.
Therefore, the session information has to be kept on the client side and be sent on each request. This information can be just the username (the server would get more information from the database) or it may include more data, like roles, permissions, etc. (making it possible to save a database access).
In the following points I’ll be implementing authentication with JSON Web Tokens (JWT), which is very common nowadays. Spring does not offer out of the box support for JWT and I had to gather information from many sources in order to get a complete picture. This post tries to summarize all the information I got.
A better explanation can be found here (JJWT is the Java library used in this post to manipulate JWT tokens), so I will quickly go over the JWT aspects that are particularly relevant. For a detailed description, you can read the specification.
JSON Web Tokens have three parts:
A header containing information such as the algorithm used to sign the token.
A payload containing claims. There are registered claims ( link to specification ), like expiration date (exp) or subject (sub), and custom “claims”, like “is_root” in the following example.
A signature of the previous two parts.
The token consists of the concatenation of the header in Base64, a dot, the body in Base64, a dot and the signature in Base64. For example:
Note that in this example, the token is signed but not encrypted (also a possibility). If you decode the two first Base64 parts you will get some perfectly readable JSON. Therefore, you may not want to include sensitive data on your tokens, like a telephone number or an email address, and you may consider using a secure connection between client and server (see token hijacking later).
The workflow for a token authentication schema is as follows:
Later, the client accesses a resource that requires authentication:
This schema presents some challenges that are new with regard to the cookie based session management:
How do you logout?
Once you generate a token it exists forever. Does that mean that it is not possible to logout? Somehow.
Loging out, as in the user hitting “loging out” and navigating away to a login form, can be implemented on the client side by just dropping the token. But the token will still be valid even if nobody uses it, so we should care about where we store it, how we transmit it to the server, etc.
It is a good strategy to set an expiration date, in order not to have many valid tokens around. Then, after the user logs out on the client, the token is still valid for a while, but eventually will go invalid.
How do you deal with an expired token?
So our tokens have an expiration date. Let’s say 10 minutes after they are created. Should the user log in again each 10 minutes? Yes, unless something is done to prevent it. For example, the client can use an about to expire token to obtain a new one. This can be automated, so that any request containing a token in the last part of its lifespan results on a response containing a new fresh token.
This way the application would log the user out if he does not use the application for a while (whatever “last part of the token lifespan” means in your application). If the user uses the application regularly he will never be logged out.
Adjusting the token lifespan to few minutes or few days gives a very different user experience.
How can the server revoke a token?
A similar problem as logout: if a user is vandalizing the application and you want to revoke his access immediately, how can you do it?
A solution could be to keep a black list of tokens. Of course this comes with a penalty because the server should check the black list in the point (5) in the workflow above.
Analogously to the session hijacking, if some attacker gets access to the token he can use it until it reaches its expiration date. In the same way, the solution is basically using a secure connection between server and client. Additionally it is possible to encrypt the token ( JWE ).
Some techniques, as browser fingerprinting, can make the hijacking harder but is far from the security level that the previous options give.
Can you imagine what would happen if somebody could generate the tokens and sign them as if he was the server? Well, that could be as easy as getting your private key compromised. A common practice is not to store secret keys in code hosted in public repositories (note the irony). That and secret rotation: automate a system in order to change the private key regularly. And, following Kerckhoffs’s principle, you should consider the rotation algorithm known to the attacker.
The following token based authentication schema implementation has the following features:
It uses JWT as tokens.
It generates tokens containing the user id in the subject (sub) claim. No role information will be included so the services using this token will have to query the database in order to get that information.
Tokens will have an expiration date.
At the end of the expiration date, a request from the client will get a response containing a new fresh token.
Because of the previous point (a token being potentially returned in any request) the tokens are returned in custom HTTP headers.
As in the previous post, we will be creating a protected resource “secret.txt” in the src/main/resources/static folder, that can be accessed in the root of our application and that contains some string like:
This is our secret!
And again as in the previous post (a quick 2 minutes read is worth if you don’t know what is going on here), we replace the default Spring Security form based login redirections:
With this departing point, the implementation consists on:
A success handler for login actions that returns the new token in a HTTP header (jwt-token).
JWTFilter: A javax.servlet.Filter that processes each request and:
Creates an Authentication instance on the SecurityContext whenever the request contains a valid token.
If the token is at the end of its lifespan it will return a new token in the jwt-new-token header.
JWTProvider: A class dealing with the token related operations: creation, extraction from the request, validation, etc.
Let’s start by the login success handler. Instead of using HTTPStatusHandler in order to just set the status code, we will need another implementation that sets the status code to 200 and returns a token. This is configured in the WebSecurityConfig class like this:
Whenever a successful login takes place, the instance of JWTStatusHandler will be called, which will create the token and return it in the jwt-token header, along with a 200 (OK) status code:
This code makes use of the JWTProvider.createToken method, which looks like this:
It provides the JJWT library with the token claims, such as expiration date and subject (username), and the algorithm and secret key to sign the token. With this information JJWT generates the token String.
Now the client will read the jwt-token header and will keep it, sending it back to the server when it requires to access a protected resource. The server will have to check for this token in each request, building an Authentication instance for the controllers. That’s what the filter does.
The filter can be installed in WebSecurityConfig, before the UsernamePasswordAuthenticationFilter, like this:
First, the filter gets the token from the request invoking getToken:
As said before, the token is sent in the Authorization header as “Bearer <token>”.
The next thing the filter does is to check if the token is valid:
Note that the parseClaimsJws method throws an exception if the token is expired, so the method code does not have to take care about that:
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
* before the time this method is invoked.
However, it must check if the algorithm used to sign the token is different to the one used to create it (ALGORITHM constant). This is very important, specially because the algorithm can be “none”! Imagine your access control layer accepting tokens that anybody could be generating.
If the token is valid, an Authentication instance is generated from the token with the getAuthentication method:
It just accesses the body of the token and gets the subject claim containing the username. With this information, it uses the usual Spring Security beans in order to load user data and generate an Authentication instance.
Let’s use a bit of curl to check our implementation. First, if we try to access the protected resource we will get a forbidden (403) response:
If we proceed to login we obtain a response with an OK (200) and a jwt-token header containing the token:
We can now use this token in the Authorization header in order to access the protected resource:
And if we wait until the token is about to expire, the response will contain a new-jwt-token header with a fresh token to use in our next requests.
Some more random thoughts that didn’t receive enough attention this time:
We didn’t deal with client-side token persistent storage. What if you want tokens with a lifespan of days? Where do you store them? Local storage? Cookies? It would be a pity that your secure connection between client and server becomes useless just because you store your token where you shouldn’t.
Are your tokens long lived? Then you have either one of these problems:
If you hold more information than the user id, like roles, etc. how do you deal with updates?
If you hold just the user id and your services queries the database to get user information, your API is not so stateless anymore.