Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Spring Security CSRF 403 Forbidden on successful login

I’m using Spring Security on a basic Thymeleaf setup with index.html and login.html, however the default login page always returns 403 Forbidden when the credentials are valid. (It gives a UI error when the credentials don’t match, as expected).

I believe it’s due to the CSRF token which is already included as a cookie (XSRF-TOKEN) in every request to backend. I’d rather not simply disable CSRF, so I’ve tried including this token into the POST request in almost every way I could find online:

  • changing target to /login?_csrf=token
  • inserting <input type="hidden" name="_csrf" ... into the Thymeleaf form (this is default behaviour and I checked that it does send, but backend rejects it??)
  • swapping from normal form submission to AJAX/fetch and inserting X-XSRF-TOKEN header. Doesn’t work too, including both JSON and x-www-form-urlencoded encoded requests.

Any ideas? What does the default Spring Security /login POST endpoint expect in the request? How does it expect the CSRF token?
Authentication seems to be working, it’s just that CSRF fails on successful login. Or is it something else entirely that I’m missing that’s giving me a 403 Forbidden?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Thanks in advance!!

My setup

Spring Boot version: 2.6.2

pom.xml dependencies

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.xerial</groupId>
            <artifactId>sqlite-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>com.zsoltfabok</groupId>
            <artifactId>sqlite-dialect</artifactId>
            <version>1.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

index.html

<form th:action="@{/login}" method="post">
    <label for="username">Username</label>
    <input type="text" name="username" />
    <label for="password">Password</label>
    <input type="password" name="password" />
    <button type="submit">Submit</button>
<!-- there's also a hidden CSRF token generated automatically -->
</form>

WebSecurityConfigurerAdapter

@Configuration
@EnableGlobalAuthentication
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // ...
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/favicon.ico").anonymous()
            .antMatchers("/static/**").anonymous()
            .mvcMatchers("/", "/login", "/signup").anonymous()
            .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/")
            .and()
            .httpBasic()
            .and()
            .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
    }
    
}

WebMvcConfigurer

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }
    
}

Controller

@Controller
@RequestMapping("/")
public class WebController {

    @GetMapping
    public String index() {
        return "index";
    }
    
}

>Solution :

The issue is your security rules.

.antMatchers("/favicon.ico").anonymous()
.antMatchers("/static/**").anonymous()
.mvcMatchers("/", "/login", "/signup").anonymous()

You configured them for anonymous access. Which means someone who isn’t authenticated yet. It will prevent an authenticated user to access any of those URLs (or un-authenticated if you disabled anonymous access).

To fix allow access for anyone using permitAll() instead of anonymous().

.antMatchers("/favicon.ico").permitAll()
.antMatchers("/static/**").permitAll()
.mvcMatchers("/", "/login", "/signup").permitAll()
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading