Spring Boot token authentication using JWT

Last week, I had a discussion with my team colleagues regarding securing Rest services and the way to handle users Authentication/Authorization. First thing that jumped to our discussion was sending basic HTTP auth headers (username/password) for every request, but that would require to keep those credentials in memory and the service would have to check those credentials (including hashing the passwords which should be an expensive operation). So that’s not the best idea.

This is why REST services typically use a token system. A standard token system returns a 'token' (just a long unique string of random characters, for example a GUID) on successful login. The client in turn then sends this token in every request’s Authorization header. The service, on every request, 'rehydrates' its context by looking up the context on the server side. This context can be stored in a DB, retrieved from a Redis cache or simply stored in memory in a hash table. The downside of this approach is that for every REST method you will need to do this lookup in the database or cache.

And then comes JSON Web Tokens, or JWT in short. JSON Web Tokens are tokens that are not only unique to a user but also contain whatever information you need for that user, the so called claims. The most basic claim is the 'subject' (basically a unique user ID) but the tokens can be extended to include any information you want. Examples would be api access rights or user roles; you can simply add a 'roles' array with the 'user' and 'admin' rights to the claims when a user logs in. These claims can then be retrieved from the JWT whenever the client sends the JWT to the server.

Obviously this token is not just plain text; that would make it trivial for a client to add an 'admin' claim to it’s set. JWT’s are either encrypted with a secure key (only known to the server) or signed. The most common approach when you use JWTs is by signing them. This 'signed' bit of the JWT is called the JWS, JSON Web Signature. You use this approach when there is only information in the token that you want the client to be able to read. The base-64 encoded content is signed but not encrypted.

Another approach is by using JWE, JSON Web Encryption. With JSON Web Encryption you use an industry standard encryption method to encrypt the contents of your token. Only the server can create and decrypt the token so this means the client can’t read or alter the contents since it doesn’t know the secret.

Show me the code!

So enough with the theory; let’s get down to some actual code. I have created a small example project that showcases the signed JWT using spring boot.

First we’ll start with the signing. It is handled by the /user/login route:

@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(@RequestBody User login) throws ServletException {

    String jwtToken = "";

    if (login.getEmail() == null || login.getPassword() == null) {
        throw new ServletException("Please fill in username and password");
    }

    String email = login.getEmail();
    String password = login.getPassword();

    User user = userService.findByEmail(email);

    if (user == null) {
        throw new ServletException("User email not found.");
    }

    String pwd = user.getPassword();

    if (!password.equals(pwd)) {
        throw new ServletException("Invalid login. Please check your name and password.");
    }

    jwtToken = Jwts.builder().setSubject(email).claim("roles", "user").setIssuedAt(new Date())
            .signWith(SignatureAlgorithm.HS256, "secretkey").compact();

    return jwtToken;
}

When a user logs in with a correct password, he receives a token that looks like:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtQGFib3VsbGFpdGUubWUiLCJyb2xlcyI6InVzZXIiLCJpYXQiOjE0ODYyMDYwNjd9.nbppPf6DIl3f3d79EGouJ1cN599R0JELjAiGHXUqSD0

This 'token' is then used on subsequent API calls from the client to the server. The standard approach here is to send an Authorization header with a "Bearer" token. The header would thus be:

 authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtQGFib3VsbGFpdGUubWUiLCJyb2xlcyI6InVzZXIiLCJpYXQiOjE0ODYyMDU3MTh9.N3quHsQvaqzpCLIPhm7-5_gvmK9TxVrKygCEiis27h

The SpringBootJwtApplication configures a Filter. Servlet filters can do all kinds of things with and to HttpRequests, we will be using this filter to protect our 'secure' endpoints. As you can see the SpringBootJwtApplication class configures our JwtFilter to act only on "/secure/*" endpoints:

    final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    registrationBean.setFilter(new JwtFilter());
    registrationBean.addUrlPatterns("/secure/*");

    return registrationBean;

This way it won’t complain when we call /user/login without an Authorization header. The filter is responsible for checking if the correct Authorization header is there and if the token in the header is valid:

public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
        throws IOException, ServletException {

    final HttpServletRequest request = (HttpServletRequest) req;
    final HttpServletResponse response = (HttpServletResponse) res;
    final String authHeader = request.getHeader("authorization");

    if ("OPTIONS".equals(request.getMethod())) {
        response.setStatus(HttpServletResponse.SC_OK);

        chain.doFilter(req, res);
    } else {

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            throw new ServletException("Missing or invalid Authorization header");
        }

        final String token = authHeader.substring(7);

        try {
            final Claims claims = Jwts.parser().setSigningKey("secretkey").parseClaimsJws(token).getBody();
            request.setAttribute("claims", claims);
        } catch (final SignatureException e) {
            throw new ServletException("Invalid token");
        }

        chain.doFilter(req, res);
    }
}

We use the Jwt parser to check the token signature with the same key we used to sign it. If the key is valid we then store the "Claims" that contains some user information (email, role) in the request object so it can be used by API endpoints down the line.

Last but not least we call chain.doFilter so that whatever is configured down the line also gets called. Without it the request would not be passed onto our controllers.

So now that we have this filter in place we can actually define some nice super safe API methods:

@RequestMapping("/user/users")
public String loginSuccess() {
    return "Login Successful!";
}

You can use a REST client like Postman play with this example.

If you have any suggestion, questions or remarks, please feel free to comment below.