How to implement JWT authentication in Spring Security and Angular – Part 3

In this part of the series we’ll look at the most complex part of the project: Generating, reading, and validating JWT tokens. Our backend server will issue a token and return it to a requesting user. When a user tries to access a restricted resource, the token gets validated and, if the user is permitted, the resource can be accessed.

Implement the JWT features

Let’s start with the AuthenticationTokenConfigurer class which will simply set up our custom security filter and link all the components:

public class AuthenticationTokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>
{
    private AuthenticationTokenProvider authenticationTokenProvider;

    public AuthenticationTokenConfigurer(AuthenticationTokenProvider authenticationTokenProvider)
    {
        this.authenticationTokenProvider = authenticationTokenProvider;
    }

    @Override
    public void configure(HttpSecurity http)
    {
        AuthenticationTokenFilter customFilter = new AuthenticationTokenFilter(authenticationTokenProvider);
        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

Okay, so right now this might not make too much sense. However, after looking at the custom filter, it should:

@Component
public class AuthenticationTokenFilter extends GenericFilterBean
{
    // The token provider is needed for token validation
    private AuthenticationTokenProvider authenticationTokenProvider;

    public AuthenticationTokenFilter(AuthenticationTokenProvider authenticationTokenProvider)
    {
        this.authenticationTokenProvider = authenticationTokenProvider;
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException
    {
        // Get the token and try to parse it
        String authenticationHeader = ((HttpServletRequest)req).getHeader(SecurityGlobals.HEADER_FIELD_NAME);
        String token = authenticationTokenProvider.getTokenFromHTTPHeader(authenticationHeader);

        // Check, whether the token was valid
        if (token != null && authenticationTokenProvider.validateToken(token))
        {
            Authentication auth = authenticationTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        // Call the next filter when we're done
        filterChain.doFilter(req, res);
    }
}

The filter chain is exactly what the name suggests: A chain of filters. You can add your own custom filters to it and each filter can read and modify the requests that get sent to the server or that the server responds with.

In this particular case, we read the value of the authentication header of an HTTP-request. After that, we try to extract the payload of that header field.

Once that’s done, the received payload gets validated and if it’s valid, we try reading the data of the user, this token was issued for.

At the end of our filter, we call the next filter in the chain.

All the token generation, extraction, and validation is done in the provider class, which is the biggest module of this project.

The JWT provider

Token generation

Now we can finally generate tokens for a user. To do that, I implemented the following method:

public String createToken(User userDetails, boolean futureToken)
{
    Date now = new Date();
    Date validity = new Date(now.getTime() + SecurityGlobals.TIME_TO_LIVE);
    Date validFrom = now;

    Claims claims = Jwts.claims()
                        .setSubject(userDetails.getUsername())
                        .setIssuer(SecurityGlobals.ISSUER)
                        .setIssuedAt(now)
                        .setNotBefore(validFrom)
                        .setExpiration(validity);

    claims.put("administrator", userDetails.getAdmin());
    claims.put("id", userDetails.getId());

    return Jwts.builder()
               .setClaims(claims)
               .signWith(SecurityGlobals.SIGNING_KEY)
               .compact();
}

So, first we get the current time. This will allow us to give the token an expiration date. Then, the claims are created. This is the second array that we saw earlier, when decoding the JWT. As you can see, you can add as many custom claims, as you wish. I added two. The isAdmin value and the ID of the requesting user.

Once that’s done, the claims are signed with a signing key. This was one error, that many (if not almost all) other tutorials got wrong. The signing key is NOT supposed to be a simple string (many use “secret” or “key” which are the worst keys possible closely followed by “password”). The JWT library has a key generation mechanism included which should be used to create the right length and type of key for the algorithm used. Everything else is prone to errors and a potential security risk.

Token validation

The following method validates a token and returns true if it’s valid

boolean validateToken(String token)
{
    if(null == token)
       return false;

    try
    {
        Jws<Claims> claims = Jwts.parser().setSigningKey(SecurityGlobals.SIGNING_KEY).parseClaimsJws(token);

        if (claims.getBody().getExpiration().before(new Date()))
            return false;
    }
    catch (SignatureException e)
    {
        // The signature of the given token did not match the locally computed one.

        // This error occurs when a user tries to use an old key or one,
        // that didn't get created by this server.
    }

    return true;
}

In this simple case, the only thing checked is the expiration time of the token. If it’s not yet expired, the token is accepted. In your implementation, you can add as many checks as needed.

Other features

The token provider has a few helper methods for parsing different fields of a JWT. However, I think it’s not necessary to discuss them in detail as they are pretty self-explanatory. You can download the code at the end of this series.

Last, but not least, make sure that the TokenProvider class is annotated as a Component.

The authentication endpoint

Now that everything’s setup, we can almost start using our application. The last thing we need is an endpoint where an unauthenticated user can enter his or her credentials and get a valid token from the backend which can be used to access a secured area.

So create a new endpoint:

@RestController
@RequestMapping("/authentication")
@CrossOrigin
public class AuthenticationEndpoint
{
    @Autowired
    AuthenticationTokenProvider authenticationTokenProvider;

    @Autowired
    IUserDao users;

    @PostMapping("/")
    @CrossOrigin
    public AuthenticationResponse createBearerToken(@RequestBody AuthenticationRequest data)
    {
        AuthenticationResponse response = new AuthenticationResponse();

        try
        {
            User user = users.findOneByUsername(data.getUsername());

            if(user == null)
            {
                // User not found in database
            }
            else if(!user.getPassword().equals(data.getPassword()))
            {
                // The supplied password didn't match the stored one
            }
            else
            {
                response.setToken(authenticationTokenProvider.createToken(user, false));
            }
        }
        catch (Exception e)
        {
            // Proper exception handling needed
        }

        return response;
    }
}

The two classes, used in the endpoint (AuthenticationRequest and AuthenticationResponse) simply contain variables and getter/setter methods for them:

public class AuthenticationRequest
{
    String username, password;

    // Getters, Setters
}
public class AuthenticationResponse
{
    private String token;

    // Getters, Setters
}

Obtaining a key

Start your application and send a POST request to the user endpoint to create a new user:

Figure 1: Create a new user

Now send a GET request to the authentication endpoint:

Figure 2: Get the newly created user’s JWT

Copy the token to the clipboard and access a secured area, for example GET /users/

Figure 3: Access a restricted area and use bearer token authentication

And, as you can see, the JWT gets accepted and a list of users is returned!

Access the authenticated user’s information in the backend

So far, all the security features were managed by Spring Security. It restricts access to certain resources and allows certain methods. But what if you want to allow all users to access a certain resource but only allow administrators to read certain values?

An example: All users should be able to access /users/x where x is a user ID. However, regular users may only access the resource if x corresponds to their own ID. Administrators should be able to get each user’s details. That’s the reason why we implemented our User entity and the UserService the way we did: It will allow us to simply access all fields of the user that sent the request.

So let’s extend the old findOneById method of the UserEndpoint so that it looks like this:

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@CrossOrigin
public User findOneById(@PathVariable("id") Long id)
{
    User loggedInUser = (User)(SecurityContextHolder.getContext().getAuthentication().getPrincipal());
    User found = userService.findOneById(id);

    if(loggedInUser.getId().equals(found.getId()) || loggedInUser.getAdmin())
        return found;
    else
        return null;
}

This will get the information of the user, that sent the request, and check, whether he’s and admin or requested his own ID. If that’s the case, the found user gets returned. Otherwise, null is returned.

Note: In such a case it would be better to throw a 403 exception instead of returning null. But for this example, it’s enough.

What’s still left to do?

You’ll need to implement proper exception handling. I omitted all of it to keep the code short and easier to understand.

The tokens will expire after five minutes and a user needs to renew them when that happens. That will be addressed in the next part of this series.

If you use an Angular frontend, you’ll need to implement an authentication and renewal mechanism. I’ll show you how I did it in the last part of this series.

Table of contents

Part 1 – Basics of JWT and the project scaffolding
Part 2 – Configure Spring Security
Part 3 – JWT token generation, validation, and authentication (You are here)
Part 4 – JWT authentication in an Angular frontend
Part 5 – Token renewal

4 thoughts on “How to implement JWT authentication in Spring Security and Angular – Part 3

Leave your two cents, comment here!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.