As I worked on my blog site, one issue I couldn't resolve in time was how to enable CSRF so that it can be used in teh AngularJS end. The problem was where I can get the CSRF token in the AngularJS code. This token is generated when Spring Security is used to create the web form and added as a hidden field. At the time, I was not not too concerned about this issue. Now that I am building a new site with existing code base. I though it would be nice to solve this issue, then integrate the solution to this new site.
It turns out that with latest Spring Boot, Spring Security, and all that. There is a solution. The way it works is that whenever user logged in via Spring Security, a secure session is created. The user credential is stored in the session, and a session specific CSRF token is also created and stored in the session. This CSRF token will be the same throughout the existence of the session. There is also a per-request CSRF token which can be more secure, but the per-session CSRF token is easier for API based access. The per request CSRF token is used primarily for MVC based application. And session based is more suitable for API based protections. So it is what I will be discussion in this post.
This is how to enable per-session CSRF token for user. It is all in this configure(HttpSecurity http)
method:
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and() .authorizeRequests() .antMatchers("/assets/**", "/public/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() ... }
I have highlighted the part where it enables CSRF token and specify the token to be stored in session so that it can be sent as part of request or response. CookieCsrfTokenRepository.withHttpOnlyFalse()
is especially important. This part creates an object of type CookieCsrfTokenRepository
. And the object will have the property Cookie Http Only to be set as false. HttpOnly for cookie will not allow JavaScript to access the data stored in the cookie. So with this part, the object will by default to be HttpOnly set as false allowing JavaScript to access the data.
This is the easy part. The hard part was figuring it out, which is not bad. A couple online search and StackOverflow posts would solve it. Another hard part is how to use this in the AngularJS code. How it is used will be discussed in the next section.
When the page is first rendered, the request and response will contain the cookie, which will be stored in the browser's cookie store. Whenever my front end code wants to send a POST, PUT, DELETE and some other similar requests, these all requires the CSRF token to be sent back for validation. Based on my testing it seems that with the latest Spring Security, the CSRF token header is added automatically when the session cookie CSRF token is presented, as a result no additional code is necessary to add the CSRF token explicitly into the ngResource calls to the server. However, if you are interested in getting the CSRF token out of the request cookies, here is how it is done, first with dependency injection of $cookies
directive:
var mod = angular.module("testSampleApp", [ "ngResource", "ngCookies" ]); mod.factory("testService", ["$resource", "$cookies", function ($resource, $cookies) { ... } ]);
To get the CSRF token value, here is how:
var csrfToken = $cookies.get("XSRF-TOKEN");
You can explicitly pass this token to the ngResource. But you don't have to. In case you want to, here is how it is done (this is only needed if you don't see the CSRF token in the request, and getting some security exception):
var apiRes = $resource(null, null, { ..., postTest: { url: "<my post destination URL>", headers: { "X-XSRF-TOKEN" : csrfToken }, method: "POST", isArray: false } }); function tryPostTest(testData) { return apiRes.postTest(testData).$promise; }
For Spring based application, the header key for CSRF token is "X-XSRF-TOKEN", the value would be the same value retrieved from the cookie store. Everything else is the same as for ngResource usage. Again, you don't have to do this explicitly.
Then I write a test method for a button on the UI. When user click on this, it will send the dummy request to the back end API service. If the configuration is correct, a success response will be returned. If the configuration is wrong, it will return security exception. Here is my test method:
vm.clickBtn2 = function () { var testData = { testValue: "I don't like this video." }; testService.tryPostTest(testData).then( function (result) { console.log(result); }, function (error) { if (error) { console.log(error); } console.log("Error happened."); } ); };
That is all there is. The whole configuration is very simple.
To test this, I will comment out the header, recompile the jar file and run it again. It should fail without the CSRF token in the header. It didn't fail. This is how I realized that I don't have to add the CSRF token header value explicitly. It is there as long as the session is valid, and request cookies has the CSRF token in it. Man. This is a super easy tutorial post. At least, I found out that I was wrong and doing unnecessary work.
There is no comments to this post/article.