5

As I know, in a Spring+JavaScript single page application, we need to send the CSRF token to the client in some way.

A recommended way would be to have a CsrfHeaderFilter as described in this Spring guide. Following this approach, when the application will start, it will send a GET request to the server, thus fetching the token.

But I see that under certain events like login or logout, Spring Security changes the token. CsrfHeaderFilter comes beforehand, and so can't detect the change. Hence, I am needing to send another GET request following such events.

I tried peeping at the Spring Security code to find if there would be a way to send the changed token along with these login or logout requests, so that another GET request is saved. But, could not find a way.

Liked to know if sending a dummy GET request after login, logout etc., as I am now doing, looks like a good solution. Or, maybe there is a better way?

If there is currently no way to avoid this redundant GET request, I was wondering if this becomes a ticket for Spring Security to come up with something after which it would be possible.

Sanjay
  • 8,755
  • 7
  • 46
  • 62
  • 1
    By default the CSRF token is stored in the HTTP session. By default, login generates a new session, discarding the previous session (if any) and does not copy anything to the new one. Likewise, logout destroys the existing session. Therefore, these events may seem to be changing the CSRF token (since the sessions are different). It is good security practice to keep these sessions separate (see the Spring Security reference documentation for details). A possible solution would be to perform a redirect to a particular URL on logout. This will force a `GET` and hence a refresh of the token. – manish Jul 27 '15 at 14:20
  • I see. Could there be a way to pass the new token in the same response? – Sanjay Jul 27 '15 at 16:26
  • I am not aware of any. – manish Jul 27 '15 at 16:36
  • I was wondering, then, if Spring Security can generate the new token before the response is written, so that the new token can be included in the responses of *login* etc. Looks like a possible ticket? – Sanjay Jul 29 '15 at 05:21
  • 2
    The token is generated at the start of a request in the CSRF filter. If it is re-generated at the end of the request (after the logout has completed), it will still be stored in the old session (because the filter does not re-generate sessions). So, the next request (which will trigger generation of a new session) will still not get the token that went out with the previous response. I am not the maintainer for Spring Security but if I was, I would not be convinced about this being a valid scenario, given that performing a redirect on logout is a valid (and in my view a logical) solution. – manish Jul 29 '15 at 05:25
  • Oh, I get it. If new fresh sessions are being created, it won't be possible to retain data from last session. Let me try if redirect would work in single page applications; otherwise sending a following *GET /ping* anyway works. – Sanjay Jul 29 '15 at 05:34
  • You may want to take a look at the [Logout success handler configuration](http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#nsa-logout) section of the official documentation. – manish Jul 29 '15 at 05:46
  • My findings on the redirect approach: I'm using CORS. Got this error when tried to redirect: "XMLHttpRequest cannot load http://localhost:8080/login. The request was redirected to 'http://localhost:8080/lemon-current-user', which is disallowed for cross-origin requests that require preflight." Looks like 302 isn't a valid response when using CORS. At least http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0 seems to indicate so (see step 3). – Sanjay Jul 29 '15 at 06:27
  • This is not a cross-origin scenario because the protocol, server and port are the same between the request and the redirect response. My suggestion would be to create a small working app that demonstrates the error and open a JIRA with the Spring Security team to comment. – manish Jul 29 '15 at 06:53
  • I've the client (AngularJS application) running at localhost:9000. – Sanjay Jul 29 '15 at 07:05

1 Answers1

2

Came across with the similar situation regarding CookieCsrfTokenRepository.

The application I worked on has a custom implementation of login with via REST-service. The service has httpServletRequest.logout() call inside that (as I figured out) led to clearing of XSRF-TOKEN cookies in response:

Set-Cookie:XSRF-TOKEN=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ibdrs; Secure

Without new XSRF-TOKEN value in the answer I had two options:

  1. Make a dummy get request to obtain new XSRF-TOKEN right after login (as the OP suggested)

  2. Get updated XSRF-TOKEN in the same login response instead of clearing cookie.

It turned out that the second option could be achieved by:

  1. Made my own CustomCookieCsrfTokenRepository as the copy of CookieCsrfTokenRepository (the source code is here). If it wasn't final it would be sufficient to extend it instead of copying.

  2. Changed all the occurrences of CookieCsrfTokenRepository to CustomCookieCsrfTokenRepository inside the copy

  3. Replaced saveToken method with new version that never clears the cookie:

    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request,
                      HttpServletResponse response) {
        if (token == null) {
            token = generateToken(request);
        }
        String tokenValue = token.getToken();
        Cookie cookie = new Cookie(this.cookieName, tokenValue);
        cookie.setSecure(request.isSecure());
        if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
            cookie.setPath(this.cookiePath);
        } else {
            cookie.setPath(this.getRequestContext(request));
        }
        cookie.setMaxAge(-1);
        if (cookieHttpOnly && setHttpOnlyMethod != null) {
            ReflectionUtils.invokeMethod(setHttpOnlyMethod, cookie, Boolean.TRUE);
        }
        response.addCookie(cookie);
    }
    
  4. Configured HttpSecurity to use the new class:

    .csrf()
        .csrfTokenRepository(CustomCookieCsrfTokenRepository.withHttpOnlyFalse())
        .and()
    
AlexK
  • 118
  • 8