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.
This is an update to this tutorial. In 2026, I have upgraded my application project using Spring Boot 4.0.0. Because of this, the old code that handles the Spring Security http request handling is no longer working. I have to rewrite the Spring Security rules for request handling. The new code looks like this:
http.csrf((x) -> {
// XXX CSRF token is clear and static, per login session.
x.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// XXX CSRF token must be validated by CsrfTokenRequestAttributeHandler()
// un-encoded token validation.
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler());
})
//.csrf((x) -> { x.disable(); }) // XXX Do not use this. No CSRF validation at all.
.authorizeHttpRequests((x) -> {
x.requestMatchers("/assets/**", "/public/**").permitAll().anyRequest().authenticated();
}).formLogin((x) -> {
x.loginPage("/login").permitAll().usernameParameter("username").passwordParameter("userpass")
.successHandler(new SavedRequestAwareAuthenticationSuccessHandler())
.defaultSuccessUrl("/secure/index", true).failureUrl("/public/authFailed");
}).logout((x) -> {
x.logoutSuccessUrl("/public/logout").permitAll();
}).exceptionHandling((x) -> {
x.accessDeniedHandler(accessDeniedHandler);
});
return http.build();
The section where CSRF token processing is specified at the beginning, like this:
http.csrf((x) -> {
// XXX CSRF token is clear and static, per login session.
x.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// XXX CSRF token must be validated by CsrfTokenRequestAttributeHandler()
// un-encoded token validation.
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler());
})
Unlike the previous http security rule, where all I needed is defining a CSRF Token Repository, I need to add a CSRF Token Request Handler of type CsrfTokenRequestAttributeHandler. It took a few hours of research to get to this point. During the implementation and testing. I found that the CSRF token was successfully passed from AngularJS code to the back end. But the server side cannot recognize the token, as result, the request cannot be validated. The back end was expecting the CSRF token in non-clear text format, the one received was in clear text format. The added csrfTokenRequestHandler() would allow clear text token to be successfully validated.
I discovered the issue with CSRF token validation while working on a "POST" request from the AngularJS side. I wasted one hour+ trying to verify the request content is correct, and is not causing the request mis-routed. Then it hit me. I didn't include the CSRF token in the request to the server side. I took the old code I have used, retrofit for the new version of Spring Security, and it failed to work. Again it took a long while for me to find that I needed a CSRF token request handler to force the back-end to accept clear text CSRF token. I relied mostly on Google's AI overview. It wasn't very helpful. I am very glad it worked out in the end. If it doesn't work, I will have to disable CSRF token validation, which is not what I wanted. Of course, there are room for improvement. It is possible to use custom cookie for CSRF token, I can also encode the token and decode the token at the server end for validation. This could add extra security to the application. I will do more research on it.
Guest comment is not allowed for this post.
There is no comments to this post/article.