Maltram Maltram - 28 days ago 15
Java Question

CSRF with Spring and Angular 2

I am trying to implent CSRF-protection with Spring Security (4.1.3) and Angular 2.0.1

There are many sources to related topics but I cannot find a clear instruction. Some of the statements even contradict each other.

I read about springs way of doing it (though the guide describes the Angular 1 way) Spring Security Guide with Angular
IT implies, that with

.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());


everything should work "out of the box".
Even further the angular guide to security describes the CSRF-protection as build in.

In my enviroment the POST looks like this:


  • There is an OPTIONS-call which returns POST, 200 OK and a XSRF-TOKEN - cookie.

  • my http.post adds an authorization-header and adds the RequestOption "withCredentials"

  • It sends three cookies, two JSessionID's and an XSRF-TOKEN that is different from the one recieved by the OPTIONS-call, no XSRF-header.

  • Debugging into the Spring CsrfFilter shows me that it looks for a header named X-XSRF-TOKEN and compares it to the token in the cookie named XSRF-TOKEN.



Why doesn't Angular send the header, too?
How is this secure if Spring only checks the provided cookie and the provided header with no serverside action whatsoever?

There are some similar questions like this but the only answer with 0 upvotes seems (to me) plain wrong, as CSRF, from my understanding, has to have a serverside check for the cookie validation.

This question only provides information on how to change the cookie or header name as explained here

What am I missing here? I doubt there is a mistake in the Spring Security implementation but I cannot quite get it to work.

Any ideas?

POST-call

login(account: Account): Promise<Account> {


let headers = new Headers({ 'Content-Type': 'application/json' });
headers.append('X-TENANT-ID', '1');
headers.append('Authorization', 'Basic ' + btoa(account.userName + ':' + account.password));
let options = new RequestOptions({ headers: headers, withCredentials:true });
return this.http.post(this.loginUrl, account, options).toPromise()
.then(this.extractData)
.catch(this.handleError)


}

Spring Security Config

[] csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())


OPTIONS header

POST header

Answer

The problem was the application path. Spring has the option to set the cookie-path in its pipeline but it is not released yet.

I had to write my own implementation for the CsrfTokenRepository which would accept a different cookie path.

Those are the relevant bits:

public final class CookieCsrfTokenRepository implements CsrfTokenRepository

private String cookiePath;

 @Override
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
    String tokenValue = token == null ? "" : token.getToken();
    Cookie cookie = new Cookie(this.cookieName, tokenValue);
    cookie.setSecure(request.isSecure());
    // cookie.setPath(getCookiePath(request));
    if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
        cookie.setPath(this.cookiePath);
    } else {
        cookie.setPath(getRequestContext(request));
    }

    if (token == null) {
        cookie.setMaxAge(0);
    } else {
        cookie.setMaxAge(-1);
    }
    if (cookieHttpOnly && setHttpOnlyMethod != null) {
        ReflectionUtils.invokeMethod(setHttpOnlyMethod, cookie, Boolean.TRUE);
    }

    response.addCookie(cookie);
}

public void setCookiePath(String path) {
    this.cookiePath = path;
}

public String getCookiePath() {
    return this.cookiePath;
}