I'm storing my authentication token on an http-only cookie. When an user logs-in, an http cookie carrying the token will be set. Here's the code to process the login:
@PostMapping("/processlogin")
@ResponseBody
public ResponseEntity<String> processLogin(
@RequestBody SigninDataDto signinDataDto,
HttpServletResponse response
) {
if(signinDataDto.getUserName()!=null && signinDataDto.getPassword()!=null) {
String userName = signinDataDto.getUserName();
String password = Common.sha256Hash(signinDataDto.getPassword());
boolean rememberMe = signinDataDto.isRememberMe();
UserDetails customUserDetails = new UserService(userRepository).loadUserByUsername(userName);
if(password.equals(customUserDetails.getPassword())) {
//Authenticate by cookie
Cookie jwtCookie = new Cookie(Constants.JWT_NAME, jwtService.generateToken((CustomUserDetails) customUserDetails));
if(rememberMe) {
jwtCookie.setMaxAge(7*24*60*60);
}
jwtCookie.setPath("/");
jwtCookie.setHttpOnly(true);
response.addCookie(jwtCookie);
return ResponseEntity.ok("success");
}
}
log.error("Login failed!");
return ResponseEntity.badRequest().body("failed");
}
Then i made a filter to authenticate each request by getting the token cookie and verify it. Here's the filter code:
package com.springdemo.library.security;
import com.springdemo.library.services.JwtService;
import com.springdemo.library.services.UserService;
import com.springdemo.library.utils.Common;
import com.springdemo.library.utils.Constants;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtService jwtService;
@Autowired
private UserService customUserDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwt(request);
if (StringUtils.hasText(jwt) && jwtService.validateToken(jwt)) {
String userName = jwtService.getUserNameFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserByUsername(userName);
if(userDetails != null) {
UsernamePasswordAuthenticationToken
authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
response.sendRedirect("/login");
}
}
} catch (Exception ex) {
log.error("failed on set user authentication", ex);
}
filterChain.doFilter(request, response);
}
private String getJwt(HttpServletRequest request) {
Cookie cookie = Common.getCookie(request, Constants.JWT_NAME);
if(cookie!=null) {
String token = cookie.getValue();
if(StringUtils.hasText(token)) {
return token;
}
}
return null;
}
}
Security config:
@Bean
protected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.authorizeRequests(authorizeRequests ->
authorizeRequests
.requestMatchers("/login", "/processlogin").permitAll()
.anyRequest().authenticated()
);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
It works fine if in the filter I don't check isHttpOnly for the jwt cookie, but if I do, the browser denies access to /Library/home. Please can anyone explain why can't the filter verify isHttpOnly for the jwt cookie, eventhough I set the cookie as HttpOnly? And any way to check if a cookie is HttpOnly in Java/Spring Boot?
Here's the JavaScript code that I used to login
$('document').ready(function () {
$('#id-form-login').on('submit', function (e) {
e.preventDefault();
$.ajax({
url: '/Library/processlogin',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
userName: $("#login-username").val(),
password: $("#login-password").val(),
rememberMe: $("#rememberme").prop("checked")
}),
success: function () {
window.location.replace("/Library/home");
},
error: function (jqXHR, textStatus, errorThrown) {
console.warn('Error:', textStatus, errorThrown);
}
});
});
});
Appreciate anyone who helps
name=valuepair, all other "options" used when the cookie was set, will not get send back to the server.