· Jose Antonio López · 19 min read
Authentication in Spring Security with JWT - Complete Guide
Step-by-step implementation and best practices to secure REST APIs in Java with JSON Web Token, persistence in PostgreSQL, and effective management of roles and permissions.

Objective
A real guide on how to configure a Spring Boot 3.4.1 project with Spring Security 6.4.2:
Why am I writing this article?
There are many articles, tutorials, and even courses that are unclear about implementing JWT authentication in Spring.
Much of the content shows the way but does not reference the official documentation and is far from real code.
Details are lost and important aspects such as saving users in the database are not covered in depth.
Prerequisites
To follow this article, you need to have installed:
Dependencies
The dependencies used are:
I chose Maven as the dependency manager because I feel more comfortable with it. If you prefer Gradle, you can use it without any problem.
Design Decisions
Onion Architecture
The architecture will be onion architecture. It allows responsibilities to be separated into three main layers:
I chose this architecture because I think it’s a good way to organize code. It’s a stepping stone for programmers who have traditionally worked with layered architectures.
Moving to a hexagonal or clean architecture can be a very abrupt change.
Below you can see the structure
auth
├── application
│ ├── AuthCookieConstants.java
│ ├── mappers
│ │ └── AuthMapper.java
│ └── services
│ ├── AuthServiceImpl.java
│ └── TokenServiceImpl.java
├── domain
│ ├── AuthException.java
│ ├── Role.java
│ ├── services
│ │ ├── AuthService.java
│ │ └── TokenService.java
│ ├── User.java
│ └── UserRepository.java
└── infrastructure
├── config
│ ├── EncoderConfig.java
│ └── SecurityConfig.java
├── controllers
│ └── AuthController.java
├── dtos
│ ├── CreateUserDto.java
│ ├── LoginRequestDTO.java
│ └── UserResponseDTO.java
├── filters
│ ├── JwtAuthenticationFilter.java
└── persistance
└── PostgresUserRepository.javaDependency Injection via Constructor
The type of dependency injection I used is via constructor. It’s the best way to inject dependencies in Spring and the recommended way in the official documentation.
If you usually use field injection, I recommend switching to constructor injection.
Lombok
To reduce boilerplate code, I used the Lombok library, which reduces visible code through annotations. In this case, annotations like @Getter, @Setter, @NoArgsConstructor, @AllArgsConstructor, and @Builder are used.
Other annotations that can be used are @Data, @EqualsAndHashCode, and @ToString, although I consider these touch on sensitive topics and recommend implementing them manually if needed.
Domain Entities Annotated with JPA
Domain entities are classes that represent things from the problem we’re solving. In this case, an entity would be User.
These classes are annotated with @Entity and @Table to indicate they are also JPA entities.
Annotating domain entities with framework annotations can be considered bad practice. The technical term is domain contamination. I decided to do it to simplify the code and avoid creating unnecessary mappers that add complexity.
JWT in a Header Called Set-Cookie
Authentication will be done with JWT and sent in a header called Set-Cookie. The main reasons are that the API will be consumed from a web client and it’s safer to send the token in a cookie with the following characteristics:
HttpOnly: The cookie is not accessible from JavaScript.Secure: The cookie is only sent over HTTPS.SameSite: The cookie is not sent in third-party requests.Max-Age: The cookie expires in X minutes.
If you want to dive deeper, I recommend reading the documentation on mozilla.org.
WebSecurityConfig Class Configuration
The WebSecurityConfig class is the main configuration for Spring Security. It configures the application’s security and defines which routes are public and which are not.
Here’s a link to the guide to implement and understand it on this same blog.
Users in Database
User Entity
The first step is to define the User entity that represents a user. The User entity implements the UserDetails interface from Spring Security.
The interface defines some default methods that are already implemented.
Personally, I don’t like having default implementations in an interface, but that’s how it is.
package com.auth.domain;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "users")
public class User implements UserDetails {
@Id
@UuidGenerator(style = UuidGenerator.Style.AUTO)
private UUID id;
private String username;
private String password;
private String firstName;
private String surnames;
@Column(unique = true)
private String email;
private String phoneNumber;
@Column(name = "role", nullable = true)
@Enumerated(EnumType.STRING)
private Role role;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(role);
}
@Override
public boolean isAccountNonExpired() {
return UserDetails.super.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return UserDetails.super.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return UserDetails.super.isCredentialsNonExpired();
}
@Override
public boolean isEnabled() {
return UserDetails.super.isEnabled();
}
}Why UserDetails?
UserDetails is an interface that represents a user in Spring Security and is used by a service called UserDetailsService.
This is necessary so that Spring Security can authenticate a user without additional code.
Explanation of Annotations
@Entity: Indicates that the class is a JPA entity. More information in the official documentation.
@Table(name = "users"): Specifies that the entity will be stored in a table named users. If not specified, JPA uses the class name as the table name.
@Id: Marks the id field as the primary key of the entity.
@UuidGenerator: Automatically generates a UUID. By default, Hibernate generates version 4 UUIDs.
@Column: Indicates that the email field is unique in the database. To keep the code concise, only essential fields are annotated with @Column.
@Enumerated(EnumType.STRING): Specifies that the role field is an Enum and will be stored as a String in the database.
User Repository in Domain
First, you need to define a repository interface in the domain. This controls the operations that make sense for the problem being solved.
package com.auth.domain;
import java.util.Optional;
import java.util.UUID;
public interface UserRepository {
Optional<User> findByEmail(String email);
User save(User user);
Optional<User> findById(UUID id);
}Additional Information
In the context of Domain-Driven Design (DDD) or patterns like Repository Pattern, a repository acts as an abstraction over the data persistence layer.
User Repository in Infrastructure
You also need to define a repository in the infrastructure. Do not confuse it with the domain repository.
package com.auth.infrastructure.persistance;
@Repository
public interface PostgresUserRepository extends JpaRepository<User, UUID>, UserRepository {
}Additional Information
The infrastructure acts on the database and allows the domain to remain independent of implementation details.
@Repository: Indicates that the class is a Spring component and will be used to access the database. Mentioned in passing in the official documentation.
PostgresUserRepository: Clearly indicates that it serves for the implementation of a PostgreSQL database.
JPARepository: Provides methods to interact with the database. Remember to pass the primary key data type as the ID. More information in the official documentation.
Tip
You can tell that the interface should go in infrastructure because it’s annotated with @Repository. You should never use Spring annotations in the domain. An exception is annotating entities with @Entity and always with proper justification.
DTOs
DTOs (Data Transfer Objects) are objects used to transfer data between layers. In this case, they are used to transfer data between the infrastructure layer and the application layer.
CreateUserDto
To create a user, a DTO is defined containing the necessary fields. You can add or remove fields according to your application’s needs. For this example, the fields email, firstName, and password are used.
package com.auth.infrastructure.dtos;
public record CreateUserDto(
@Email
@NotBlank
String email,
@NotBlank
String firstName,
@NotBlank
String password
) {
}Annotation Explanation
@NotBlank: Indicates that the field cannot be empty. @Email: Indicates that the field must be a valid email. It might not be perfect, but it’s a good start.
Tip
A DTO is an object with immutable information and should never be modified. For DTOs, I always recommend using record introduced in Java 14. Use it in Java 17 or higher, where it became stable.
LoginRequestDTO
The following DTO is for login and contains the necessary fields to authenticate a user.
package com.auth.infrastructure.dtos;
public record LoginRequestDTO(
@Email
@NotBlank
String email,
@NotBlank
String password
) {
}UserResponseDTO
The last DTO is used to return user data.
package com.auth.infrastructure.dtos;
public record UserResponseDTO(
UUID id,
String name,
String email,
Role role
) {
}Very Important
Never return sensitive data such as the user’s password. The UserResponseDTO does not return the user’s password.
AuthMapper
The AuthMapper is a class that transforms DTOs to domain entities or vice versa. It’s good practice to have a mapper dedicated solely to transforming data between layers.
package com.auth.application.mappers;
public class AuthMapper {
private AuthMapper() {
throw new UnsupportedOperationException("This class should never be instantiated");
}
public static User fromDto(final CreateUserDto createUserDto) {
return User.builder()
.email(createUserDto.email())
.firstName(createUserDto.firstName())
.build();
}
public static Authentication fromDto(final LoginRequestDTO loginRequestDTO) {
return new UsernamePasswordAuthenticationToken(loginRequestDTO.email(), loginRequestDTO.password());
}
public static UserResponseDTO toDto(final User user) {
return new UserResponseDTO(user.getId(), user.getFirstName(), user.getEmail(), user.getRole());
}
}Tips
For mapping entities, I like to follow these best practices:
- Do not use mapping libraries: I prefer not to use libraries like MapStruct. I like to have full control and avoid unnecessary dependencies.
- Purely static class: The
AuthMapperclass is purely static. It’s a utility class with only static methods. - Do not instantiate the class: The
AuthMapperclass should not be instantiated and throws an exception if instantiation is attempted. I useUnsupportedOperationExceptionto indicate to other developers that the class should not be instantiated.
AuthService
AuthService in Domain
This service is one of the most important and handles logic related to authentication and user management.
package com.auth.domain.services;
public interface AuthService {
String login(LoginRequestDTO loginRequestDTO);
boolean validateToken(String token);
String getUserFromToken(String token);
void createUser(CreateUserDto createUserDto);
User getUser(UUID id);
}Tip
During development, I was tempted to extend UserDetailsService directly. That wouldn’t be entirely correct because UserDetailsService is a Spring Security interface and shouldn’t be used in the domain layer.
AuthService Implementation in Application
package com.auth.application.services;
@Service
public class AuthServiceImpl implements AuthService, UserDetailsService {
private static final Logger logger = LogManager.getLogger(AuthServiceImpl.class);
private final UserRepository userRepository;
private final TokenService tokenService;
private final PasswordEncoder passwordEncoder;
private final AuthenticationConfiguration authenticationConfiguration;
public AuthServiceImpl(
UserRepository userRepository,
TokenService tokenService,
PasswordEncoder passwordEncoder,
AuthenticationConfiguration authenticationConfiguration
) {
this.userRepository = userRepository;
this.tokenService = tokenService;
this.passwordEncoder = passwordEncoder;
this.authenticationConfiguration = authenticationConfiguration;
}
@Override
public void createUser(final CreateUserDto createUserDto) {
final User createUser = AuthMapper.fromDto(createUserDto);
createUser.setPassword(passwordEncoder.encode(createUserDto.password()));
final User user = userRepository.save(createUser);
logger.info("[USER] : User successfully created with id {}", user.getId());
}
@Override
public User getUser(final UUID id) {
return userRepository.findById(id)
.orElseThrow(() -> {
logger.error("[USER] : User not found with id {}", id);
return new FincasException(FincasErrorMessage.USER_NOT_FOUND);
});
}
@Override
public String login(final LoginRequestDTO loginRequest) {
try {
final AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
final Authentication authRequest = AuthMapper.fromDto(loginRequest);
final Authentication authentication = authenticationManager.authenticate(authRequest);
return tokenService.generateToken(authentication);
} catch (Exception e) {
logger.error("[USER] : Error while trying to login", e);
throw new ProviderNotFoundException("Error while trying to login");
}
}
@Override
public boolean validateToken(final String token) {
return tokenService.validateToken(token);
}
@Override
public String getUserFromToken(final String token) {
return tokenService.getUserFromToken(token);
}
@Override
public UserDetails loadUserByUsername(final String username) {
return userRepository.findByEmail(username)
.orElseThrow(() -> {
logger.error("[USER] : User not found with email {}", username);
return new UsernameNotFoundException("User not found");
});
}
}Code Explanation
@Service: Indicates that the class is a Spring component and will be used for business logic.
loadUserByUsername: Method from the UserDetailsService interface that Spring Security calls internally.
AuthenticationConfiguration: Class that configures authentication in Spring Security.
Tip
Here it makes sense to implement UserDetailsService. The interface is from Spring Security and the service implementation is from the application. AuthenticationConfiguration helps break a circular dependency that appeared between AuthService and SecurityConfig. The circular dependency appears between AuthenticationManager and AuthService.
Secret Key and Expiration Time
application.yml
We’ll use application.yml with custom properties. Configuring these parameters in a properties file is good practice because they can be changed without recompiling the code.
application:
security:
jwt:
secret-key: 9a8b7c6d5e4f3g2h1i0j9k8l7m6n5o4p3q2r1s0t
expiration: 15 # minutesExplanation of Properties
secret-key: Secret key to sign the JWT token. It’s important that it’s a secure key and not shared with anyone.
expiration: JWT token expiration time in minutes. It’s usually set in milliseconds, but for simplicity, I set it in minutes.
The expiration time will depend a lot on the application and the security you want to implement. If the expiration time is too short, users will have to log in constantly. If it’s too long, the token can be stolen and used by an attacker. I think 15 minutes is enough for most applications.
EncoderConfig
JWT encoding and decoding is done in Spring Security via Beans. In this case, implementations from Nimbus JOSE + JWT SDK are used.
@Configuration
package com.auth.infrastructure.config;
public class EncoderConfig {
@Value("${application.security.jwt.secret-key}")
private String jwtKey;
@Bean
JwtEncoder jwtEncoder() {
return new NimbusJwtEncoder(new ImmutableSecret<>(jwtKey.getBytes()));
}
@Bean
JwtDecoder jwtDecoder() {
byte[] bytes = jwtKey.getBytes();
SecretKeySpec originalKey = new SecretKeySpec(bytes,0,bytes.length,"HmacSHA256");
return NimbusJwtDecoder.withSecretKey(originalKey).macAlgorithm(MacAlgorithm.HS256).build();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}What Does Nimbus JOSE + JWT SDK Do?
Spring Security provides the JwtEncoder and JwtDecoder interfaces to encode and decode JWTs. They are functional interfaces with an encode and decode method, respectively.
The implementations provided in Spring Security are NimbusJwtEncoder and NimbusJwtDecoder.
Nimbus JOSE + JWT SDK is a Java library that allows the creation and verification of JSON Web Tokens (JWT) and JSON Web Signature (JWS).
The JwtEncoder is like a machine that does three things:
- Takes your information (user data)
- Converts it into a special format called JWT (JSON Web Token)
- Digitally “signs” it using a secret key (provided by the JWKSource)
The JWS Compact Serialization format is the way the information is organized so all systems can understand and verify the tokens.
The JWKSource part is the key keeper. It provides the secret key to “seal” (sign) the token.
What Does BCryptPasswordEncoder Do?
BCryptPasswordEncoder is an implementation of PasswordEncoder that uses the BCrypt encryption algorithm. Under the hood, it uses a hash function. Hash functions take an input and return an output. They are one-way functions, meaning there’s no way to discover the original password from the output.
TokenService in Domain
The TokenService is the service responsible for generating and validating JWT tokens.
package com.hey.auth.domain.services;
import org.springframework.security.core.Authentication;
public interface TokenService {
String generateToken(Authentication authentication);
String getUserFromToken(String token);
boolean validateToken(String token);
}TokenService in Domain or Application?
I decided to put it in the domain layer. In my view, the application is a REST API and the token is part of the domain. Security and the future application would coexist in the same project. If security were in a microservice, the project would move as is by moving the auth folder to the new microservice.
TokenService Implementation in Application
The implementation of TokenService is responsible for generating and validating JWT tokens. In this article, the Nimbus JOSE + JWT SDK library is used.
package com.auth.application.services;
@Service
public class TokenServiceImpl implements TokenService {
private final static Logger logger = LogManager.getLogger(TokenServiceImpl.class);
@Value("${application.security.jwt.secret-key}")
private String secretKey;
@Value("${application.security.jwt.expiration}")
private int jwtExpiration;
private final JwtEncoder jwtEncoder;
private final JwtDecoder jwtDecoder;
public TokenServiceImpl(JwtEncoder jwtEncoder, JwtDecoder jwtDecoder) {
this.jwtEncoder = jwtEncoder;
this.jwtDecoder = jwtDecoder;
}
@Override
public String generateToken(Authentication authentication) {
Instant now = Instant.now();
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
User currentUser = (User) authentication.getPrincipal();
JwtClaimsSet claims = JwtClaimsSet.builder()
.subject(currentUser.getEmail())
.issuedAt(now)
.expiresAt(now.plus(jwtExpiration, ChronoUnit.MINUTES))
.build();
var jwtEncoderParameters = JwtEncoderParameters.from(JwsHeader.with(MacAlgorithm.HS256).build(), claims);
return this.jwtEncoder.encode(jwtEncoderParameters).getTokenValue();
}
@Override
public String getUserFromToken(String token) {
Jwt jwtToken = jwtDecoder.decode(token);
return jwtToken.getSubject();
}
@Override
public boolean validateToken(String token) {
try {
jwtDecoder.decode(token);
return true;
} catch (Exception exception) {
logger.error("[USER] : Error while trying to validate token", exception);
throw new BadJwtException("Error while trying to validate token");
}
}
}Additional Information
Code
@Value: Indicates that the property will be injected from the properties file.JwtClaimsSet: Class representing the claims of the JWT token.JwtEncoderParameters: Class representing the parameters of the JWT token. It stores the signing algorithm and claims.JwtClaimsSet.Builder: Class that allows building the JWT token. You can add claims and set the expiration date.
Claims
Claims are the information stored in the token. In this case, the user’s email and expiration date are stored. The scope claim is a claim you can use to store information about permissions. In this case, I don’t use it for anything, but it’s a good example of how to retrieve permissions. Later, you can use the scope claim to store information about permissions.
AuthService
Initially, the AuthService had all the logic for generating and validating tokens. I decided to move it to its own service to separate responsibilities. Anyway, it’s valid for AuthService to have token generation and validation logic. Personally, I prefer TokenService to handle token generation and validation, and AuthService to authenticate and manage users.
JWTAuthenticationFilter
The JWTAuthenticationFilter is the filter responsible for validating that the JWT token is valid and that the user has permission to access the route.
package com.auth.infrastructure.filters;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
private final AuthService authService;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(AuthService authService, UserDetailsService userDetailsService) {
this.authService = authService;
this.userDetailsService = userDetailsService;
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
final String requestURI = request.getRequestURI();
return requestURI.equals(SecurityConfig.LOGIN_URL_MATCHER);
}
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull FilterChain filterChain) throws ServletException, IOException {
final Optional<String> token = getJwtFromCookie(request);
if (token.isEmpty() || !authService.validateToken(token.get())) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
throw new BadCredentialsException("Invalid token");
}
String userName = authService.getUserFromToken(token.get());
UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(userDetails);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
private Optional<String> getJwtFromCookie(HttpServletRequest request) {
final Cookie[] cookies = request.getCookies();
if (cookies == null || ArrayUtils.isEmpty(cookies)) {
return Optional.empty();
}
return (Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals(AuthCookieConstants.TOKEN_COOKIE_NAME))
.map(Cookie::getValue)
.findFirst());
}
}Additional Information
Code
OncePerRequestFilter: Spring Security recommends extending OncePerRequestFilter to create custom filters. This filter runs once per request. You can read more in the official documentation.shouldNotFilter: The filter does not run if the route is login. During login, there is no token, so it shouldn’t trigger.doFilterInternal: The token is validated and the values extracted from the token are passed to the Spring Security context.HttpServletRequest: Represents the incoming HTTP request.HttpServletResponse: Represents the outgoing HTTP response. A 401 error is returned if the token is invalid.FilterChain: Used to pass the request to the next filter.getJwtFromCookie: Retrieves the token from theSet-Cookieheader. If there is no token, an empty Optional is returned.UsernamePasswordAuthenticationToken: Represents an authentication token. Stores the user and the user’s permissions.BadCredentialsException: Specific Spring Security exception for when the token is invalid.
Tip
The JwtAuthenticationFilter runs before the controller handles the request. If the token is invalid, the controller does not run and a 401 error is returned. This is a clean way to handle security in the application.
In other frameworks like NestJS, this part is usually handled with an interceptor inside the middleware. In Spring Security, it’s done with a jakarta.servlet.Filter, which is part of the Servlet API.
It’s also important not to declare the filter as a @Component. The filter may be run by both the Spring container and Spring Security. Declare it without the @Component or @Bean annotation. You can find more information in the official documentation.
AuthController
The AuthController manages requests related to user login and logout. In the future, user management can be added, but for now, only login and logout will be managed.
package com.auth.infrastructure.controllers;
@RestController
@RequestMapping(ApiConfig.API_BASE_PATH + "/auth")
public class AuthController {
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
@PostMapping
public void createUser(@RequestBody @Valid CreateUserDto createUserDto) {
authService.createUser(createUserDto);
}
@PostMapping("/login")
public void login(@RequestBody @Valid LoginRequestDTO loginRequestDTO, HttpServletResponse response) {
final String token = authService.login(loginRequestDTO);
final Cookie cookie = createAuthCookie(token);
response.addCookie(cookie);
}
@PostMapping("/logout")
public void logout(HttpServletResponse response) {
final Cookie cookie = new Cookie(AuthConstants.TOKEN_COOKIE_NAME, StringUtils.EMPTY);
cookie.setMaxAge(0);
}
private Cookie createAuthCookie(String token) {
final String SAME_SITE_KEY = "SameSite";
final Cookie cookie = new Cookie(AuthConstants.TOKEN_COOKIE_NAME, token);
cookie.setHttpOnly(AuthConstants.HTTP_ONLY);
cookie.setSecure(AuthConstants.COOKIE_SECURE);
cookie.setMaxAge(AuthConstants.COOKIE_MAX_AGE);
cookie.setAttribute(SAME_SITE_KEY, AuthConstants.SAME_SITE);
return cookie;
}
}Login Endpoint
The login endpoint simply passes the data from the request body to the AuthService. If the login is successful, the service generates a String called token. To attach the token to the response, a cookie is created using the jakarta.servlet.http.Cookie class.
The token is stored in a cookie called auth-token and sent to the client. The cookie has the following properties:
HttpOnly: The cookie is not accessible from JavaScript.Secure: The cookie is only sent over HTTPS.SameSite: The cookie is not sent in third-party requests.Max-Age: The cookie expires in X minutes.
SameSite Strict is the strictest setting and prevents the cookie from being sent in third-party requests. Sending in third-party requests means the cookie is not sent if the request does not go to the same domain.
If you need more flexibility in the type of cookie, you can check the attribute in the MDN documentation.
Logout Endpoint
The logout endpoint deletes the cookie from the client. In Java, the simplest option is:
- Create a cookie with the same name as the one you want to delete.
- Set the cookie’s lifetime to 0.
- Send the cookie to the client.
Setting the value to StringUtils.EMPTY ensures the cookie has no value. Along with maxAge set to 0, the cookie is deleted from the client.
Cookie Constants
To avoid domain contamination, I created a class AuthConstants that contains the cookie constants.
package com.auth.application.AuthCookieConstants;
public class AuthConstants {
private AuthConstants() {
throw new UnsupportedOperationException("This class should never be instantiated");
}
public static final String TOKEN_COOKIE_NAME = "auth-token";
public static final boolean HTTP_ONLY = true;
public static final boolean COOKIE_SECURE = true;
public static final int COOKIE_MAX_AGE = 60 * 12; // 12 min
public static final String SAME_SITE = "Strict";
}Cleaner Alternative to AuthCookieConstants
If you don’t want to create a purely static class with constants, you can use a properties file. In this case, the properties file would look like:
application:
security:
cookie:
name: auth-token
http-only: true
secure: true
max-age: 60 * 12 # 12 min
same-site: StrictLater, you can import the properties into the AuthController class and use them directly. This approach is cleaner as it decouples configuration from implementation. I’ll detail this implementation better when I create an exclusive entry for Cookies
References
- Spring Security
- Spring Boot