Describe the bug CsrfAuthenticationStrategy does not check for an existing token and always saves a null token when called. When used with CookieCsrfTokenRepository, this results in three Set-Cookie headers in the response. For example:

http :8080/login -a user:password

produces a response like:

HTTP/1.1 200 
Set-Cookie: XSRF-TOKEN=e97cdf5e-0dbc-45d3-8720-d2731023c3fa; Path=/
Set-Cookie: XSRF-TOKEN=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/
Set-Cookie: XSRF-TOKEN=0dec2144-d4e6-479e-8c56-e70e61423cb6; Path=/
...

Note: The fix for gh-12141 addressed a related problem, originally discussed in gh-12094.

To Reproduce See sample.

  1. http :8080/login -a user:password
  2. Observe that POST /login contains three Set-Cookie headers for XSRF-TOKEN.

Expected behavior Because the request did not contain an XSRF-TOKEN cookie, only a single Set-Cookie header containing the generated token should be returned in the response.

Sample

Spring Security Version: 5.8.0-SNAPSHOT (after 6b0ed0205b9bcfe28f6e4d7f6e1b4ab8f583f424).

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .authorizeHttpRequests((authorize) -> authorize
                .anyRequest().authenticated()
            )
            .csrf((csrf) -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            )
            .httpBasic(Customizer.withDefaults());
        // @formatter:on
        return http.build();
    }

    @Bean 
    public UserDetailsService userDetailsService() {
        // @formatter:off
        UserDetails userDetails = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();
        // @formatter:on
        return new InMemoryUserDetailsManager(userDetails);
    }

}