Spring v2.1.4.RELEASE

CookieCsrfTokenRepository is a dedicated class to make CSRF integration with AngularJS work out of the box / pretty easy.

In an AngularJS application it's enough enable CSRF by using the CookieCsrfTokenRepository and AngularJS automatically adds the CSRF header to POST and other requests: Enable CSRF in the WebConfig through http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())`.

Bug Summary: In CookieCsrfTokenRepository the CSRF header name is hard wired as "X-XSRF-TOKEN". The intercepting CsrfFilter is not able to retrieve the actual token because the request object's header names are stored in lowercase x-xsrf-token but the lookup uses the hard wired upper case value X-XSRF-TOKEN (see code).

In depth: Once CSRF is enabled requests from AngularJS to Spring are intercepted by the CsrfFilter. That filter determines the token's header name from the CookieCsrfTokenRepository (in which it is set to X-XSRF-TOKEN) . It tries to read that header value from the requests headerMap through String actualToken = request.getHeader(csrfToken.getHeaderName()); . All the headers from the request's headerMap store the header names in lowercase. Thus actualToken can't be set to the token value as "x-xsrf-token" != "X-XSRF-TOKEN".

Proper Requests from AngularJS that include the header X-XSRF-TOKEN end up with logging error 2019-12-10 09:48:36.633 DEBUG 18896 --- [nio-8080-exec-4] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost:8080/api/. The frontend shows Request Method Not Supported (405).

CC @rwinch as he's the author of CookieCsrfTokenRepository.

Comment From: springfan

The following change in the class CookieCsrfTokenRepository fixes the issue:

Replace

static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN";

with

static final String DEFAULT_CSRF_HEADER_NAME = "x-xsrf-token";

Comment From: gtiwari333

Hi @springfan ,

You can update the header name and cookie name using CookieCsrfTokenRepository.setHeaderName(String ), setCookieName() method respectively

See below:


        http
            .csrf()
            .csrfTokenRepository(getCsrfTokenRepository())

    CookieCsrfTokenRepository getCsrfTokenRepository() {
        CookieCsrfTokenRepository repo = CookieCsrfTokenRepository.withHttpOnlyFalse();
        repo.setHeaderName("THE NEW NAME");
        repo.setCookieName("THE NEW NAME");
        return repo;
    }

Comment From: fhanik

Header names should be case insensitive.

Given that there is a working mitigation through configuration, I'm marking this as an enhancement and requesting feedback from @rwinch prior to making changes.

I'm thinking that

String actualToken = request.getHeader(csrfToken.getHeaderName());

Should be (in the scenario where the header map is all in lowercase)

String actualToken = request.getHeader(csrfToken.getHeaderName().toLowerCase());

Comment From: rwinch

@springfan Thank you for the report.

As mentioned and stated in HttpServletRequest.getHeader(String) Javadoc, the header name is case insensitive. For that reason, This seems like a bug elsewhere and I don't see an issue with the Spring Security code.

Should be (in the scenario where the header map is all in lowercase)

We should NOT make changes to convert the header name to lowercase. What if there is another circumstance where only uppercase works? This issue really needs addressed at the root of the problem. For that reason I'm removing it from the 5.3.0.M1 milestone

The frontend shows Request Method Not Supported (405) .

An HTTP 405 seems strange. Spring Security will send an HTTP 403. It does not send a 405.

Comment From: springfan

The workaround @gtiwari333 suggested works for me:

        http
            .csrf()
            .csrfTokenRepository(getCsrfTokenRepository())

    CookieCsrfTokenRepository getCsrfTokenRepository() {
        CookieCsrfTokenRepository repo = CookieCsrfTokenRepository.withHttpOnlyFalse();
        repo.setHeaderName("x-xsrf-token");
        return repo;
    }

An HTTP 405 seems strange. Spring Security will send an HTTP 403. It does not send a 405.

I stand corrected. It's a 405 but the error is Method Not Allowed with message 'POST' not supported: {"timestamp":"2019-12-13T09:23:35.948+0000","status":405,"error":"Method Not Allowed","message":"Request method 'POST' not supported","trace":"org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported\r\n\tat org.springframework.web.servlet.support.WebContentGenerator.checkRequest(WebContentGenerator.java:380)\r\n\tat ... }

I agree that the problem relies in the implementation of the HttpServletRequest interface as its getHeader method behaves contrary to the doc and is not case insensitive.

Comment From: springfan

Here is the concrete class that implements the HttpServletRequest in the mentioned case:

Spring Security CookieCsrfTokenRepository doesn't work properly with CsrfFilter

Comment From: rwinch

If this is still an issue, please provide a complete and minimal sample that reproduces the issue. As mentioned before headers should be case sensitive, so it should not matter what the case is.

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: spring-projects-issues

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.