Implementing Token based Authentication in a Spring Boot Project

Salman Mohamed
4 min readMay 14, 2024

--

In this blog post, we will implement a Token-based Authentication system from scratch using Spring Boot 3 and Spring Security 6. This comprehensive guide will walk you through the essential steps to secure your applications effectively.

The application flow we are going to implement includes the following steps:

  1. User Registration: The user registers themselves with our application.
  2. User Login: The user logs in with their credentials to receive an access token.
  3. Token-Based Access: Using the access token, the user can access all other API endpoints within the application.
Application flow with Token based Authentication

Our task is to create a custom Authentication filter that inspects incoming requests for an access token. If the request does not contain an access token or if the token is expired or invalid, the request will not be authenticated and will be filtered out.

Creating a Filter class:

@Component
public class BearerTokenAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");

if(authHeader != null && authHeader.startsWith("Bearer ") && !authHeader.substring(7).isBlank()) {
String accessToken = authHeader.substring(7);
User user = isTokenValid(accessToken);

if(user == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
else {
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(user.getId(), null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}

filterChain.doFilter(request, response);
}
}

We create a filter class extending OncePerRequestFilter class. In the doFilterInternal method, we implement the logic for the filtration.

We search for the Bearer token in the headers and extract the token from it. Using the isTokenValid method, we validate the token. If the token is invalid, we set the response code to 401 Unauthorized and filter out the request.

We will discuss in detail about the isTokenValid() method later in the blog.

If the token is valid, the method will return the user object, which is then stored in the SecurityContextHolder as an Authentication object. By storing this object, the request is marked as authenticated.

Inserting the Filter into the Security Filter Chain:

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {

@Autowired
BearerTokenAuthFilter bearerTokenAuthFilter;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http
.authorizeHttpRequests(request -> {
request.requestMatchers("/register", "/login").permitAll();
request.anyRequest().authenticated();
})
.csrf(AbstractHttpConfigurer::disable)
.addFilterAfter(bearerTokenAuthFilter, BasicAuthenticationFilter.class);
return http.build();
}

}

We are creating a configuration class to define a security filter chain bean. By adding the authorizeHttpRequests configuration, we filter requests that are not authenticated in the authorization filter. Note that we are whitelisting two endpoints, /register and /login, allowing them to pass through the filter chain even if they are not authenticated.

Next, we integrate our custom filter, BearerTokenAuthFilter, into the filter chain. We do this by using the addFilterAfter() method, which inserts our custom filter after the BasicAuthenticationFilter. This method ensures that the BearerTokenAuthFilter is executed at the appropriate point within the filter chain.

To view all the filters that a request passes through, we set the debug parameter to true in the @EnableWebSecurity annotation. This configuration will log the details of the filters in the request processing chain, aiding in debugging and providing better visibility into the security process.

Additional Considerations for Implementation:

  1. The isTokenValid() method:

This method receives the access token and verifies its authenticity. Upon successful validation, it returns the associated User object.

The pivotal aspect here is that token validation can be approached in two ways, based on the token type: JWT token or Opaque token.

  • JWT token: A JSON Web Token containing encoded information.
  • Opaque token: A random string with no built-in details.

Here, we’ll focus on validating an opaque token. Unlike JWTs, opaque tokens don’t carry data inside. So, we must keep details like the respective user and token expiry stored separately, usually in a database table.

private User isTokenValid(String token) {
OauthToken oauthToken = tokenRepository.findById(token).orElse(null);

if(oauthToken == null || oauthToken.getExpirationTime().isBefore(LocalDateTime.now())) {
return null;
}
return oauthToken.getUser();
}

2. Controller logic for /register and /login endpoints:

/register — Creating a new user in our database.

/login — Receives the user’s login credentials and returns the access token if they are valid. The details about the access token is stored in the respective table in database for validation purposes.

In conclusion, we’ve walked through the process of creating a custom authentication filter to establish a token-based authentication system within a Spring Boot application.

For further exploration on Spring and Security-related topics, feel free to check out more articles on my profile.

--

--

Salman Mohamed

🌟 Backend Developer | Java ☕ | Spring Boot | Spring Security 🔒| Microservices | Docker 🐳 | OAuth |