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. The backend server issues tokens and returns them to users who requested one. When a user tries to access a restricted resource, they have to submit a previously generated token which the server validates. If the token validation is successful, the user may access the requested resource.

Implement the JWT features

Let’s start with the AuthenticationTokenConfigurer class which will 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 custom filters to it and each filter can read and modify the requests that users send to the server or that the server sends out to clients.

In this particular case, we read the value of the authentication header of each HTTP request. After that, the program extracts the payload of that header field.

Once that’s done, the server validates the payload and if it’s valid, the program reads the user data of the client this token was issued for.

The last line of the custom filter calls 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 allows the server to give each token an expiration date. Then, the server generates the claims. 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 user who requested the token.

Once that’s done, the claims are signed with a signing key. This was one aspect 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 includes a key generation mechanism that should be used to create the correct length and type of key for the encryption algorithm in use. 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 server accepts the token. In your implementation, you can add as many checks as you need.

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 set up, we can almost start using the application. The last thing we need is an endpoint where unauthenticated users can enter their credentials and obtain a valid token from the backend which can be used to access a secured resource.

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) 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 the 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

As you can see, the server accepts the JWT and returns a list of users!

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 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 snippet retrieves the information of the user who sent the request. Then, the code checks whether the requesting user is an administrator or the user requested their own account data. If either is the case, the server returns the user data. Otherwise, it returns null.

Note: In such a case it would be better to throw a 403 exception instead of returning null. But this method is sufficient for this tutorial.

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

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

  1. Thanks so much for this, mate! Implementing those security features was causing me so much trouble. Interesting, how hard it was to find a proper tutorial on that subject.

    Liked by 1 person

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 )

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.