├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── dist └── spring-security-csrf-token-interceptor.min.js ├── package.json └── src └── spring-security-csrf-token-interceptor.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | 27 | # OS generated files # 28 | ###################### 29 | .DS_Store 30 | .DS_Store? 31 | ._* 32 | .Spotlight-V100 33 | .Trashes 34 | ehthumbs.db 35 | Thumbs.db 36 | 37 | # IDE Files # 38 | ############# 39 | *.iml 40 | .idea/ 41 | 42 | # Maven Files # 43 | ############### 44 | target/ 45 | target-grunt/ 46 | src/main/webapp/static/node_modules/ 47 | 48 | # Git Files # 49 | ############# 50 | *.orig 51 | 52 | # Bower files # 53 | ############### 54 | node_modules/ 55 | generated/ 56 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "angular": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Allan Ditzel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module.exports = function (grunt) { 18 | 19 | grunt.initConfig({ 20 | pkg: grunt.file.readJSON('package.json'), 21 | jshint: { 22 | files: ["src/<%= pkg.name %>.js"] 23 | }, 24 | ngmin: { 25 | default: { 26 | src: ['src/<%= pkg.name %>.js'], 27 | dest: 'generated/<%= pkg.name %>.js' 28 | } 29 | }, 30 | uglify: { 31 | options: { 32 | mangle: false 33 | }, 34 | default: { 35 | files: { 36 | 'dist/<%= pkg.name %>.min.js': ['generated/<%= pkg.name %>.js'] 37 | } 38 | } 39 | } 40 | }); 41 | 42 | grunt.loadNpmTasks('grunt-contrib-jshint'); 43 | grunt.loadNpmTasks('grunt-contrib-uglify'); 44 | grunt.loadNpmTasks('grunt-ngmin'); 45 | 46 | grunt.registerTask('default', ['jshint', 'ngmin', 'uglify']); 47 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Allan Ditzel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #spring-security-csrf-token-interceptor 2 | 3 | > An AngularJS interceptor that will include the CSRF token header in HTTP requests. 4 | 5 | > It does this by doing an AJAX HTTP HEAD call to / by default, and then retrieves the HTTP header 'X-CSRF-TOKEN' and sets this 6 | same token on all HTTP requests. 7 | 8 | `spring-security-csrf-token-interceptor` also supports configuring the CSRF header name, number of retries allowed in-case of Forbidden errors, restrict adding the CSRF tokens to some HTTP types etc. 9 | 10 | #Installing 11 | ###Via Bower 12 | ```` 13 | $ bower install spring-security-csrf-token-interceptor 14 | ```` 15 | ###Via NPM 16 | ```` 17 | $ npm install spring-security-csrf-token-interceptor 18 | ```` 19 | 20 | #Usage 21 | Include this as a dependency on your application: 22 | 23 | ````javascript 24 | angular.module('myApp', ['spring-security-csrf-token-interceptor']); 25 | ```` 26 | Use the `configProvider` to customize the interceptor behavior. Check [Configuration](#Configuration) section for more details. 27 | 28 | ````javascript 29 | csrfProvider.config({}); 30 | ```` 31 | #Configuration 32 | The following options are available for configuring the interceptor, 33 | 34 | ```` 35 | Note: All these below configurations are optional. 36 | ```` 37 | 38 | - `options` (Object) - Options to customize the CSRF interceptor behavior. 39 | 40 | - `options.url` (String) - The URL to which the initial CSRF request has to be made to get the CSRF token. Default: `\`. 41 | 42 | - `options.csrfHttpType` (String) - The HTTP method type which should be used while requesting the CSRF token call. Default: `head`. 43 | 44 | - `options.maxRetries` (Number) - The number of retries allowed for CSRF token call in-case of [403 Forbidden](http://en.wikipedia.org/wiki/HTTP_403) response errors. Default: `5`. 45 | 46 | - `options.csrfTokenHeader` (Array) - Set this option to add the CSRF headers only to some HTTP requests. Default: `['GET', 'HEAD', 'PUT', 'POST', 'DELETE']`. 47 | 48 | - `options.csrfTokenHeader` (String) - Customize the name of the CSRF header on the requests. Default: `X-CSRF-TOKEN`. 49 | 50 | ###Example 51 | 52 | ```js 53 | angular 54 | .module('myApp', [ 55 | 'spring-security-csrf-token-interceptor' 56 | ]) 57 | .config(function(csrfProvider) { 58 | // optional configurations 59 | csrfProvider.config({ 60 | url: '/login', 61 | maxRetries: 3, 62 | csrfHttpType: 'get', 63 | csrfTokenHeader: 'X-CSRF-XXX-TOKEN', 64 | httpTypes: ['PUT', 'POST', 'DELETE'] //CSRF token will be added only to these method types 65 | }); 66 | }).run(function() { 67 | }); 68 | ``` -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spring-security-csrf-token-interceptor", 3 | "version": "0.2.0", 4 | "authors": [ 5 | "Allan Ditzel " 6 | ], 7 | "description": "An AngularJS interceptor for the HTTP service that adds the Spring Security CSRF token to all requests (if available).", 8 | "main": "src/spring-security-csrf-token-interceptor.js", 9 | "keywords": [ 10 | "spring-security", 11 | "csrf", 12 | "token", 13 | "http", 14 | "interceptor" 15 | ], 16 | "license": "Apache 2.0", 17 | "homepage": "https://github.com/aditzel/spring-security-csrf-token-interceptor", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "test", 23 | "tests" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /dist/spring-security-csrf-token-interceptor.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";angular.module("spring-security-csrf-token-interceptor",[]).factory("csrfInterceptor",["$injector","$q",function($injector){var $q=$injector.get("$q"),csrf=$injector.get("csrf"),csrfService=csrf.init();return{request:function(config){return csrfService.settings.httpTypes.indexOf(config.method.toUpperCase())>-1&&(config.headers[csrfService.settings.csrfTokenHeader]=csrfService.token),config||$q.when(config)},responseError:function(response){var $http,newToken=response.headers(csrfService.settings.csrfTokenHeader);return 403===response.status&&csrfService.numRetries -1) { 36 | config.headers[csrfService.settings.csrfTokenHeader] = csrfService.token; 37 | } 38 | return config || $q.when(config); 39 | }, 40 | responseError: function(response) { 41 | var $http, 42 | newToken = response.headers(csrfService.settings.csrfTokenHeader); 43 | 44 | if (response.status === 403 && csrfService.numRetries < csrfService.settings.maxRetries) { 45 | csrfService.getTokenData(); 46 | $http = $injector.get('$http'); 47 | csrfService.numRetries = csrfService.numRetries + 1; 48 | return $http(response.config); 49 | } else if (newToken) { 50 | // update the csrf token in-case of response errors other than 403 51 | csrfService.token = newToken; 52 | } 53 | // Fix for interceptor causing failing requests 54 | return $q.reject(response); 55 | }, 56 | response: function(response) { 57 | // reset number of retries on a successful response 58 | csrfService.numRetries = 0; 59 | return response; 60 | } 61 | }; 62 | } 63 | ]).factory('csrfService', [ 64 | 65 | function() { 66 | var defaults = { 67 | url: '/', // the URL to which the CSRF call has to be made to get the token 68 | csrfHttpType: 'head', // the HTTP method type which is used for making the CSRF token call 69 | maxRetries: 5, // number of retires allowed for forbidden requests 70 | csrfTokenHeader: 'X-CSRF-TOKEN', 71 | httpTypes: ['GET', 'HEAD', 'PUT', 'POST', 'DELETE'] // default allowed HTTP types 72 | }; 73 | return { 74 | inited: false, 75 | settings: null, 76 | numRetries: 0, 77 | token: '', 78 | init: function(options) { 79 | this.settings = angular.extend({}, defaults, options); 80 | this.getTokenData(); 81 | console.log(this.settings, this.defaults, options); 82 | }, 83 | getTokenData: function() { 84 | var xhr = new XMLHttpRequest(); 85 | xhr.open(this.settings.csrfHttpType, this.settings.url, false); 86 | xhr.send(); 87 | 88 | this.token = xhr.getResponseHeader(this.settings.csrfTokenHeader); 89 | this.inited = true; 90 | } 91 | }; 92 | 93 | } 94 | ]).provider('csrf', [ 95 | 96 | function() { 97 | var CsrfModel = function CsrfModel(options) { 98 | return { 99 | options: options, 100 | csrfService: null 101 | }; 102 | }; 103 | 104 | return { 105 | $get: ['csrfService', 106 | function(csrfService) { 107 | var self = this; 108 | return { 109 | init: function() { 110 | self.model = new CsrfModel(self.options); 111 | self.model.csrfService = csrfService; 112 | self.model.csrfService.init(self.model.options); 113 | return self.model.csrfService; 114 | } 115 | }; 116 | } 117 | ], 118 | 119 | model: null, 120 | 121 | options: {}, 122 | 123 | config: function(options) { 124 | this.options = options; 125 | } 126 | }; 127 | } 128 | ]).config(['$httpProvider', 129 | function($httpProvider) { 130 | $httpProvider.interceptors.push('csrfInterceptor'); 131 | } 132 | ]); 133 | }()); --------------------------------------------------------------------------------