Menu

CSRF Protection with AngularJS and Spring

May 18, 2016 by Christopher Sherman

Spring Framework, and other web application frameworks like it, comes with Cross-Site Request Forgery (CSRF) protection built-in and enabled by default. However, many tutorials assume that you will be performing a full post-back for each request, thereby allowing the CSRF token to be included in a hidden metadata attribute on the view.

When building single-page applications (SPAs) with frameworks like AngularJS, we need a way to track updates to the CSRF token without reloading the view. In this post I’ll explain how to include the CSRF token value in the header of HTTP responses and have Angular include the token in the header for non-idempotent (POST, PUT, DELETE) requests.

To include the CSRF token in our responses, we need to write a filter that extends off of Spring’s OncePerRequestFilter. To start, add the class below:

/\*

- Copyright 2014 Allan Ditzel
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
  \*/
  package com.website.app.security;

import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CsrfTokenResponseHeaderBindingFilter extends OncePerRequestFilter {
protected static final String REQUEST_ATTRIBUTE_NAME = "\_csrf";
protected static final String RESPONSE_HEADER_NAME = "X-CSRF-HEADER";
protected static final String RESPONSE_PARAM_NAME = "X-CSRF-PARAM";
protected static final String RESPONSE_TOKEN_NAME = "X-CSRF-TOKEN";

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, javax.servlet.FilterChain filterChain) throws ServletException, IOException {
CsrfToken token = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);

if (token != null) {
response.setHeader(RESPONSE_HEADER_NAME, token.getHeaderName());
response.setHeader(RESPONSE_PARAM_NAME, token.getParameterName());
response.setHeader(RESPONSE_TOKEN_NAME , token.getToken());
}
filterChain.doFilter(request, response);
}
}

Next we need to tell our applicationContext.xml and web.xml files about this filter.

In applicationContext.xml we declare our bean and provide a name.

<filter>
<filter-name>csrfTokenResponseHeaderBindingFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>csrfTokenResponseHeaderBindingFilterBean</param-value>
</init-param>
</filter>

<filter-mapping>
    <filter-name>csrfTokenResponseHeaderBindingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

If you build your project and inspect your HTTP responses, you should now see the CSRF token information we specified in the custom filter included in the header of each response. What we need to do next is to write an AngularJS interceptor to track the CSRF token value and add it to our requests. The interceptor, found below, caches the value of the CSRF token on each response and includes it on the HTTP request types we define at the top of the service.

var appServices = angular.module('app.services');

appServices.factory('CsrfTokenInterceptorService', ['$q',
    function CsrfTokenInterceptorService($q) {

        // Private constants.
        var CSRF_TOKEN_HEADER = 'X-CSRF-TOKEN',
            HTTP_TYPES_TO_ADD_TOKEN = ['DELETE', 'POST', 'PUT'];

        // Private properties.
        var token;

        // Public interface.
        var service = {
            response: onSuccess,
            responseError: onFailure,
            request: onRequest,
        };

        return service;

        // Private functions.
        function onFailure(response) {
            if (response.status === 403) {
                console.log('Request forbidden. Ensure CSRF token is sent for non-idempotent requests.');
            }

            return $q.reject(response);
        }

        function onRequest(config) {
            if (HTTP_TYPES_TO_ADD_TOKEN.indexOf(config.method.toUpperCase()) !== -1) {
                config.headers[CSRF_TOKEN_HEADER] = token;
            }

            return config;
        }

        function onSuccess(response) {
            var newToken = response.headers(CSRF_TOKEN_HEADER);

            if (newToken) {
                token = newToken;
            }

            return response;
        }
    }]);

Once you have this service file included in your application, the final step is to configure the interceptor to make the AngularJS $httpProvider aware of it. I do this configuration in my app.js file. A sample configuration is below.

angular.module('app.services', [])
.config(function($httpProvider) {
        $httpProvider.interceptors.push('CsrfTokenInterceptorService');
});

AngularJS Spring