4

I have three spring boot applications running on the same server with different paths. All three of them expose API endpoints and one of them also serve web resources such as HTML, JavaScript and CSS.

Application 1:

  1. Serves UI files
  2. Serves API endpoints

Application 2

  1. Serves API Endpoints

Application 3

  1. Serves API Endpoints

So far we only enabled CSRF validation for application 1. Which worked well with org.springframework.security.web.csrf.CookieCsrfTokenRepository. We send XSRF-TOKEN as cookie and angularJs sends back X-XSRF-TOKEN in header in each request.

Now we are planning to introduce XSRF to other two applications the same way we did for Application 1.

But we are stuck with a problem. AngularJs sends the XSRF-TOKEN from application 1 and uses the same token for all three applications while each application has its own TOKEN cookies per (application path).

This causes CSRF validation to fail for other two services.

Here are the configurations that I use.

Spring-boot version     : 1.5.3
Angular version         : 1.3.18

  <beans:bean id="csrfTokenRepository" class="org.springframework.security.web.csrf.CookieCsrfTokenRepository">
    <beans:property name="cookieHttpOnly" value="false" />
  </beans:bean>

The Error that I get

{
  "timestamp": 1509437659613,
  "status": 403,
  "error": "Forbidden",
  "message": "Invalid CSRF Token '2fa60cb2-803f-4b2b-a1d6-7e10e56ca649' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'.",
  "path": "/application2/posturl/path"
}

Here the token 2fa60cb2-803f-4b2b-a1d6-7e10e56ca649 is from application1 with cookie path /application1.

My observations so far:

  1. I checked and made sure all three applications are setting cookies to be httpOnly=false.
  2. I can see that all three applications have their own XSRF-TOKEN cookies in the chrome developer console with their own paths.
  3. I did not write a single line at the angular end to change its default behaviour.
  4. All three applications are running as WAR files on the same IP and Port.

What I suspect here is that Angular is not honouring the path attribute of the cookies and it goes with the first cookie with a name XSRF-TOKEN.

Is there a way to go around this issue?

Raja Anbazhagan
  • 4,092
  • 1
  • 44
  • 64

2 Answers2

1

You have to change the default behavior of Angularjs and Spring On Spring Security, you should change the xrsft cookie name (One different for each of the 3 applications).

On Angular, you can add an interceptor on each request that dynamically sets the correct cookie value in the header (retrieving it by $cookies.get(key));

Marco
  • 741
  • 3
  • 18
  • Is there a way i can do this without using different xsrf cookie names...? – Raja Anbazhagan Nov 01 '17 at 10:34
  • A cookie is a single key/value pair. If you don't want to change server-side cookie names. You can always create client-side cookies by adding an interceptor on all responses. – Marco Nov 02 '17 at 08:45
  • There are attributes like `httpOnly`, `secure` and `path` for a cookie that i know of... You can see the same when you open cookies section of Application tab in chrome dev tools – Raja Anbazhagan Nov 02 '17 at 08:50
0

I got the issue resolved by changing the csrfTokenRepository definition on all three applications along with an interceptor in AngularJs to read those cookies based on request URLs with all the cookies being set to same Path.

<beans:bean id="csrfTokenRepository" 
    class="org.springframework.security.web.csrf.CookieCsrfTokenRepository">
    <beans:property name="cookieHttpOnly" value="false" />
    <beans:property name="cookiePath" value="/" />
    <beans:property name="cookieName" value="APP-1-XSRF-TOKEN" />
    <beans:property name="headerName" value="APP-1-X-XSRF-TOKEN" />
</beans:bean>

Note that the cookieName and headerName are different for each applications. (Note that the headerName doesn't have to different in each of those applications. But I prefer it this way.)

The AngularJs interceptor will look something like this.

app.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.interceptors.push(function ($q) {
        return {
            'request': function (config) {
                var readCookie = function (k, r) {
                    return (r = RegExp('(^|; )' + encodeURIComponent(k) + '=([^;]*)').exec(document.cookie)) ? r[2] : null; //CafePasta from https://stackoverflow.com/a/5639455/2557818
                };
                if (config.url.indexOf("/app1") > 0) {
                    config.headers['APP-1-X-XSRF-TOKEN'] = readCookie("APP-1-XSRF-TOKEN", document.cookie);
                } else if (config.url.indexOf("/app2") > 0) {
                    config.headers['APP-2-X-XSRF-TOKEN'] = readCookie("APP-2-XSRF-TOKEN", document.cookie);
                } else if (config.url.indexOf("/app3") > 0) {
                    config.headers['APP-3-X-XSRF-TOKEN'] = readCookie("APP-3-XSRF-TOKEN", document.cookie);
                }
                return config || $q.when(config);
            }
        };
    });
}]);
Raja Anbazhagan
  • 4,092
  • 1
  • 44
  • 64