In this article, I will show the cookie based authentication with Spring Security. I will authenticate a stateless application with the cookies. And I will show the differences with the JWT authentication.
Content:
- Configuration of Spring Security
- The HTTP Filters
- The Token in the Cookie
- @AuthenticationPrincipal
Check this video for more details.
All the code is available in this repository.
Configuration of Spring Security
I’ve already added the web configuration to specify the CORS: the allowed methods and headers. Let’s start by adding the Spring Security dependency.
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
private static final Long MAX_AGE = 3600L;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders(
HttpHeaders.AUTHORIZATION,
HttpHeaders.CONTENT_TYPE,
HttpHeaders.ACCEPT)
.allowedMethods(
HttpMethod.GET.name(),
HttpMethod.POST.name(),
HttpMethod.PUT.name(),
HttpMethod.DELETE.name())
.maxAge(MAX_AGE)
.allowedOrigins("*")
.allowCredentials(true);
}
}
And now let’s add the security configuration.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
What I will add in the security configuration is:
- an exception handler to return a pretty JSON response instead of stacktraces;
- configure the HTTP filters to handle the cookie-based authentication and the username password authentication;
- the protected endpoints;
- and some other basic configuration.
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserAuthenticationEntryPoint userAuthenticationEntryPoint;
public SecurityConfig(UserAuthenticationEntryPoint userAuthenticationEntryPoint,
UserAuthenticationProvider userAuthenticationProvider) {
this.userAuthenticationEntryPoint = userAuthenticationEntryPoint;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().authenticationEntryPoint(userAuthenticationEntryPoint)
.and()
.addFilterBefore(new UsernamePasswordAuthFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(new CookieAuthenticationFilter(), UsernamePasswordAuthFilter.class)
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().logout().deleteCookies(CookieAuthenticationFilter.COOKIE_NAME)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/v1/signIn", "/v1/signUp").permitAll()
.anyRequest().authenticated();
}
}
But which is the best authentication system: the cookie based or JWT? What I want using the cookie-based authentication is to be stateless, to avoid having a user session stored in the application. Because this way, I can hardly handle multiple instances of the same application, and I can’t recover the information if my application stops. Using JWT, I store some necessary information in the token. The necessary information to tell the application that the user is correctly connected. And what I will do with the cookie is very similar. I will store a few pieces of information to let the application know the user is already authenticated. The JWT has some content, some information, and an encrypted signature with a secret key to ensure the validity of the token. And I will reproduce this behavior with the cookie. I will store some basic information, like the login and the user id, and an encrypted signature.

So, why use the cookie instead of the JWT? The token can be provided by a third application, can be shared between multiple applications until its end date is reached. On the other hand, when I create the cookie I can specify on which domain the cookie must be used. And the browser will help me maintain this cookie safe. I know that this cookie won’t be used by a third party.

Of course it will be harder to use for SSO, for single signed on systems, where I need a third party system to authenticate into the application. In this case, the JWT is the best option.
Let’s now start creating the entry point, which catches the exceptions.
@Component
public class UserAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
OBJECT_MAPPER.writeValue(response.getOutputStream(), new ErrorDto("Unauthorized path"));
}
}
The HTTP Filters
The Username Password Filter
Let’s now continue creating the username password filter.
public class UsernamePasswordAuthFilter extends OncePerRequestFilter {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
protected void doFilterInternal(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
if ("/v1/signIn".equals(httpServletRequest.getServletPath())
&& HttpMethod.POST.matches(httpServletRequest.getMethod())) {
CredentialsDto credentialsDto = MAPPER.readValue(httpServletRequest.getInputStream(), CredentialsDto.class);
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(credentialsDto.getLogin(), credentialsDto.getPassword()));
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
This HTTP filter will only catch the request against the “signIn” endpoint. Read the login and password information, and store it in the security context. I will check its validity later. And call the remaining filters at the end.
The Cookie based filter
Let’s go now to create the cookie-based filter.
public class CookieAuthenticationFilter extends OncePerRequestFilter {
public static final String COOKIE_NAME = "auth_by_cookie";
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
Optional<Cookie> cookieAuth = Stream.of(Optional.ofNullable(httpServletRequest.getCookies()).orElse(new Cookie[0]))
.filter(cookie -> COOKIE_NAME.equals(cookie.getName()))
.findFirst();
if (cookieAuth.isPresent()) {
SecurityContextHolder.getContext().setAuthentication(
new PreAuthenticatedAuthenticationToken(cookieAuth.get().getValue(), null));
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
I need to name my cookie and look for it. And as before, if it’s present, store the value in the security context that I will read later. And very important, call the remaining filters at the end.
Okay, but those filters don’t have the logic to know if the user, if the request, is correctly authenticated. I’ve added some information in the security context but nothing was validated. Let’s now add the authentication provider. This component will read the security context and validate its content to return a complete authentication object with the user information.
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {
private final AuthenticationService authenticationService;
public UserAuthenticationProvider(AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UserDto userDto = null;
if (authentication instanceof UsernamePasswordAuthenticationToken) {
// authentication by username and password
userDto = authenticationService.authenticate(
new CredentialsDto((String) authentication.getPrincipal(), (char[]) authentication.getCredentials()));
} else if (authentication instanceof PreAuthenticatedAuthenticationToken) {
// authentication by cookie
userDto = authenticationService.findByToken((String) authentication.getPrincipal());
}
if (userDto == null) {
return null;
}
return new UsernamePasswordAuthenticationToken(userDto, null, Collections.emptyList());
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
In the first if-else condition I will handle the authentication via the username and password. And in the second if-else condition I handle the authentication based on the cookie value, on the cookie token.
If I get a user from the previous methods, it means that the incoming information is correctly assigned to an existing user. Which means that the request is correctly authenticated. And I can return the user object in the authentication object. I will use it later, in the controllers.
The Token in the Cookie
Let’s go now to the authentication service, which checks the credentials, create and validate the token from the cookie.
@Value("${security.jwt.token.secret-key:secret-key}")
private String secretKey;
This secret key will be used to create the signature.
public UserDto authenticate(CredentialsDto credentialsDto) {
String encodedMasterPassword = passwordEncoder.encode(CharBuffer.wrap("the-password"));
if (passwordEncoder.matches(CharBuffer.wrap(credentialsDto.getPassword()), encodedMasterPassword)) {
return new UserDto(1L, "Sergio", "Lema", "login", "token");
}
throw new RuntimeException("Invalid password");
}
This method is to check the username password validity. I just check it with a hard-coded password. I should compare it with a stored password per user, but this is beyond the scope of this video.
public String createToken(UserDto user) {
return user.getId() + "&" + user.getLogin() + "&" + calculateHmac(user);
}
I will create a token as the concatenation of the id of the user, its login, and a signature. I will use HMAC. Which is a hash based message authentication code.
private String calculateHmac(UserDto user) {
byte[] secretKeyBytes = Objects.requireNonNull(secretKey).getBytes(StandardCharsets.UTF_8);
byte[] valueBytes = Objects.requireNonNull(user.getId() + "&" + user.getLogin()).getBytes(StandardCharsets.UTF_8);
try {
Mac mac = Mac.getInstance("HmacSHA512");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKeyBytes, "HmacSHA512");
mac.init(secretKeySpec);
byte[] hmacBytes = mac.doFinal(valueBytes);
return Base64.getEncoder().encodeToString(hmacBytes);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
To find a user from a token, I first split the token into three parts to get the login or the id. And validate the signature, otherwise, it means that the token doesn’t correspond to this user.
public UserDto findByToken(String token) {
String[] parts = token.split("&");
Long userId = Long.valueOf(parts[0]);
String login = parts[1];
String hmac = parts[2];
UserDto userDto = findByLogin(login);
if (!hmac.equals(calculateHmac(userDto)) || userId != userDto.getId()) {
throw new RuntimeException("Invalid Cookie value");
}
return userDto;
}
@AuthenticationPrincipal
Okay, now I have all the requests authenticated by cookie or by a username and password. But how do I create the cookie when the authentication is correctly done by username and password? I must create the cookie after the user is correctly authenticated with its password. Let’s go to the authentication controller.
@PostMapping("/signIn")
public ResponseEntity<UserDto> signIn(@AuthenticationPrincipal UserDto user,
HttpServletResponse servletResponse) {
Cookie authCookie = new Cookie(CookieAuthenticationFilter.COOKIE_NAME, authenticationService.createToken(user));
authCookie.setHttpOnly(true);
authCookie.setSecure(true);
authCookie.setMaxAge((int) Duration.of(1, ChronoUnit.DAYS).toSeconds());
authCookie.setPath("/");
servletResponse.addCookie(authCookie);
return ResponseEntity.ok(user);
}
Now, that I have the user object in the security context, I can use the following annotation in the controllers, @AuthenticationPrincipal. Which will inject the user object into my controller. If I reach this controller and no exception is caught by the entry point, it means that my user is correctly authenticated. So let’s create the cookie.
One last step, the logout. I must clear the security context and remove the cookie.
@PostMapping("/signOut")
public ResponseEntity<Void> signOut(@AuthenticationPrincipal UserDto user) {
SecurityContextHolder.clearContext();
return ResponseEntity.noContent().build();
}
And I will let the browser delete the cookie.
Conclusion
- First, the user will try to authenticate via username and password.
- The request will be intercepted by the HTTP filter.
- It will start the security context with some incomplete information.
- Then, the authentication provider will validate the password and return the user object.
- When reaching the controller with the @AuthenticationPrincipal annotation, I have access to the user information. I must create the cookie and store the token on it. The cookie is created in the response and return it to the user, to the browser, where it will be stored for the following requests.
- The cookie will be intercepted by another HTTP filter, which will again fill the security context with some incomplete information.
- And the authentication provider will validate the token stored in the cookie to return the user object and let the request continue to whatever controller was requested.
- And in my new controller, if I need the user object, I just need to add the @AuthenticationPrincipal annotation.


Leave a comment