CSRF/XSRF protection for Spring Security and AngularJS - angularjs

I tried to add CSRF/XSRF protection to my application, but ran into strange behavior. All get requests work fine, but on all post/put/delete I'm getting 403 Unauthorized. And the strangest thing is that when I tried to debug my CSRF filter, requests do not reach it, they are rejected somewhere earlier. They do not even reach my authentication filter, so I can not figure out what the problem may be.
My security config:
#Override
public void configure(HttpSecurity http) throws Exception {
http
...
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf().csrfTokenRepository(csrfTokenRepository());
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
I do not add the filters since as I said, the requests do not reach them. But if needed I will complete my question. I hope for your help, thank you in advance!

In principle, the CSRF mechanism in Spring stores the CSRF token in a HTTP only cookie. Because JavaScript cannot access a HTTP only cookie, you need to tell spring to disable HTTP only:
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
Then you can read the cookie from Angular and add it to the XSRF-TOKEN header with each request.
This is the general case. I am not sure if this fits your special case.

Assuming that the rest of your config/filters working properly, you're facing this issue because of this: SessionCreationPolicy.STATELESS.
You can have a look under the hood of Spring CsrfFilter. You'll see that it needs to remember the value of each CSRF-token for each user inside a session, and since you are not using sessions it can't be done.
What to do next - is really up to you. Some people saying that if you app is stateless there is actually no need for CSRF protection. Spring docs saying that CSRF attacks are still relevant. I think it really depends on your authentication mechanism.
You might also want to look at this nice article, for example.
Hope it helps.

Many thanks for the answers, they really helped me to find a solution. And I want to share my solution if in the future someone will face the same issue.
As noted in the answers I used SessionCreationPolicy.STATELESS and did not have sessions so instead of HttpSessionCsrfTokenRepository I had to use CookieCsrfTokenRepository with withHttpOnlyFalse() to allow AngularJS to read cookies.
As a result, I have a configuration like this:
#Override
public void configure(HttpSecurity http) throws Exception {
http
...
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf().csrfTokenRepository(csrfTokenRepository());
}
If someone is interested in how the CsrfHeaderFilter looks:
public class CsrfHeaderFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie==null || token!=null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
}
My second problem was CORS. AngularJS documentation says:
"The header will not be set for cross-domain requests."
To solve this problem, I had to use an HTTP Interceptor:
.factory('XsrfInterceptor', function ($cookies) {
return {
request: function (config) {
var headerName = 'X-XSRF-TOKEN';
var cookieName = 'XSRF-TOKEN';
config.headers[headerName] = $cookies.get(cookieName);
return config;
}
};
});
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('XsrfInterceptor');
}]);
I hope my answer will be useful.

Related

Getting a CORS issue when posting data from frontend(React)

I'm posting data from react to spring boot backend using axios. Please refer the below react code.
const handleSubmit = (e) =>{
e.preventDefault()
alert( "Username "+ username + " Password "+password);
axios.post('http://localhost:8080/sign-up',{
username:username,
password:password})
};
Below is my spring code.
#PostMapping("/sign-up")
public String signUp(#RequestBody User user) {
if(userRepository.findByUsername(user.getUsername())==null) {
Set<Role> roles= new HashSet<>();
Role role= roleRepository.findByName("USER");
roles.add(role);
user.setRoles(roles);
user.setPassword(bcryptEncoder.encode(user.getPassword()));
userRepository.save(user);
return "Success";
}
else{
return "Username Already Exsist";
}
}
When I post data from front end i'm getting below response in the browser console.
Access to XMLHttpRequest at 'http://localhost:8080/sign-up' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
xhr.js:178 POST http://localhost:8080/sign-up net::ERR_FAILED
createError.js:16 Uncaught (in promise) Error: Network Error
at createError (createError.js:16)
at XMLHttpRequest.handleError (xhr.js:83)
I've inserted following line in the controller of the spring boot.
#CrossOrigin(origins = "http://localhost:3000")
But the above solution didn't work. I suspect during the post call option call is happening to check the resource and in that case only this cors issue is occuring. Appreciate any input to this issue.
I had a similar issue recently.
I got a first step further by removing spring-boot-starter-security from my POM temporarily, because it was blocking the call giving unauthorized. So I figured out that that was my problem. I had to configure CORS for Spring security and then I got it to work. Apologies for the vague answer, but I hope it can help you look in another direction that could help you.
I have found the solution for the above problem. This cors issue is coming when there is a communication happening between different domains. So I have added below line in the configure() method of SecurityConfig class.
http.cors();
Then I have implemented the following CustomCorsFilter class.
#Component
public class CustomCorsFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PATCH, PUT, DELETE, OPTIONS");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, Content-Type, Allow, authorization, content-type, xsrf-token");
httpServletResponse.addHeader("Access-Control-Expose-Headers", "xsrf-token");
if ("OPTIONS".equals(httpServletRequest.getMethod())) {
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
}
Now there is no cors issue.

Client-side cookie-based authentication with Spring Security

We have a fully-working back-end login POST service, implemented using Spring Security, along with Spring Boot and Spring Session. A user needs to be logged-in in order to access other services. The login operation works, and so does the mechanism to restrict/allow access to the other services. This has been tested with Postman, which is "smart enough" to keep the session cookie on successive requests.
Now, we are trying to build the client on React. When using the browser's debug we can see the session cookie is sent in the response header without problems. We were trying to get the session cookie from the header and store it for successive requests, but it doesn't work. When investigating we learnt we are not meant to read the response header from the code, as explained here and here.
Our login operation should redirect to /customer/home, which works in Postman but not on our application. The behaviour we get with this is a 403 Forbidden, and the way we assess it is because the cookie is not set when redirecting, and hence the second operation (GET /customer/home) fails and returns 403. Is our understanding correct? However, the browser does not seem to keep the session cookie automatically. How are we supposed to maintain the session for subsequent requests if the cookie is not set automatically, and we are not supposed to read it manually? Are we supposed to NOT use cookies for this purpose, and issue authentication tokens instead?
We are obviously misunderstanding or missing something. Any pointers please?
Our WebSecurityConfigurerAdapter:
#EnableWebSecurity
#Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationProviderService authenticationProviderService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/customer/register").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.permitAll()
.defaultSuccessUrl("/customer/home", false)
.and()
.logout()
.permitAll()
.and()
.httpBasic();
http.csrf().disable();
}
//[ . . . ]
}
Our client trying to do a POST:
const mw = store => next => action => {
if(action.type == 'SUBMIT_LOGIN_USER') {
var payload = {
username: action.user.username,
password: action.user.password
};
// Build formData object.
let formData = new FormData();
formData.append('username', action.user.username);
formData.append('password', action.user.password);
return fetch('http://192.168.0.34:8080/login', {
method: 'POST',
body: formData
}).then(
r => (r)
)
.then(function(response) {
console.log(document.cookie) //empty
console.log(response.headers.get('Set-Cookie')) //null
next(action)
})
.catch(function(err) {
console.info(err);
});
} else {
next(action)
}
}
Using JWT (JSON Web Tokens) is a great way to implement security on single page applications like React.
If you're going with the JWT approach it would be efficient to use a package like axios to for http requests from the client side. Axios allows you to easily add an authorization token to all requests without hassle.
Even if you're not using JWT try using axios to send authorization tokens efficiently.

Spring Security and Angular token based login success but subsequent requests fails with 401

I'm developing an Angular application with a Spring based REST backend. Now I have the following issue.
I can login and get the Token from my angular application without an issue. But subsequent requests fail with 401 error. When I try the same rest endpoint in Postman with the token obtained from angular login component, it works fine.
I'm using HttpSessionStrategy, not OAuth, JWT.
I suspect some issue in my spring security config though I cannot figure it out. Could you please help me resolve this issue.
Following is the relevant part of my Spring security config.
#Bean
public SessionRepository<ExpiringSession> sessionRepository() {
return new MapSessionRepository();
}
#Bean
public HttpSessionStrategy httpSessionStrategy() {
HeaderHttpSessionStrategy strategy = new HeaderHttpSessionStrategy();
strategy.setHeaderName(headerName);
return strategy;
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"));
configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/**").hasAnyAuthority("ADMIN", "OPERATOR")
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(new RESTAuthenticationEntryPoint())
.and().cors().configurationSource(corsConfigurationSource())
.and().csrf().disable()
.formLogin()
.successHandler(new RESTAuthenticationSuccessHandler(objectMapper, userDetailService))
.failureHandler(new RESTAuthenticationFailureHandler())
.and()
.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
}
And, following is the failed request and response headers of my Angular application.
Instead of using the wildcard value (*) for Access-Control-Allow-Headers, enumerate the allowed headers:
configuration.setAllowedHeaders(Arrays.asList("x-auth-token"));
From the Docs:
Access-Control-Allow-Headers
Compatibility notes
The wildcard value (*) that is mentioned in the latest specification, is not yet implemented in browsers:
Chromium: Issue 615313
Firefox: bug 1309358
Servo: Issue 13283
— MDN HTTP Reference (Access-Control-Allow-Headers)

Invalid CSRF Token in POST request

Overview
I am going to use API Gateway as the authentication which based on Spring security. I've just been following the steps in the https://spring.io/guides/tutorials/spring-security-and-angular-js/ link to create a project based on "pairs-double" module of its corresponding github project of https://github.com/spring-guides/tut-spring-security-and-angular-js.git.
Problem
The issue is the fact that when any POST request is submitted to the server the "Invalid CSRF Token" exception is thrown. An example of the thrown exception is as follows:
{
"timestamp": 1461714933215,
"status": 403,
"error": "Forbidden",
"message": "Invalid CSRF Token '1cdc44ad-43cb-44e6-b903-bec24fe903fd' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'.",
"path": "/ui/test"
}
I checked an rechecked the issue but to no avail. I tested this scenario with postman and set the 'X-XSRF-TOKEN' as the header of the POST request but nothing happened.
So, as I am beginner in using Spring security approaches, I would appreciate it if anyone could suggest me a solution.
Looking at the security configuration of that project, you will notice that a XSRF-TOKEN cookie is being added in each request using a filter. So what you have to do is take the value of that cookie and store it in X-XSRF-TOKEN header. I've made a test project with similar security configuration to test out this case, the complete code looks like this:
#RestController
#SpringBootApplication
public class TestApplication extends WebSecurityConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**") // Disable authentication for all requests.
.permitAll()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), SessionManagementFilter.class); // Register csrf filter.
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
// Token is being added to the XSRF-TOKEN cookie.
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String testGet() {
return "hello";
}
#RequestMapping(value = "/test", method = RequestMethod.POST)
public String testPost() {
return "works!";
}
}
To test this out with postman do the following:
Enable interceptor to start capturing cookies.
Perform a GET /test request and open the cookies tab. There you should notice a cookie with a name XSRF-TOKEN.
Take the value of that cookie and put it in X-XSRF-TOKEN header and perform a POST /test request.

Angular JS and Spring MVC #RestController - POST status 0

I am currently try to develop RESTful application using Angular on font-end and Spring MVC on back-end(#RestController). I already have implemented few GET and POST method to fetch or save data, however now I am facing a problem with one POST method:
angular.module("app").factory("BorrowerService", function($http, $log){
var borrowBook = function(borrow){
$log.debug("Borrowing book: " );
$log.debug(borrow);
$http({
method: 'POST',
url: 'Zadanie3/borrow',
data: borrow
}).then(function successCallback(response) {
$log.debug("success");
}, function errorCallback(response) {
$log.error("failed to borrow the book");
$log.error(response);
});
};
return{
borrowBook: borrowBook
};
});
POST result is always errorCallback with output:
failed to borrow the book
Object {data: null, status: 0, config: Object, statusText: ""}
Interesting part is that on most browsers (except Firefox) the book actually do borrow. I have already searched about status:0 response (also on stackoverflow) and unfortunatelly have not found the correct solution to my problem. I would appreciate any help. Here my RestController method:
#RestController
public class BookRestPostController {
#RequestMapping(value = "/borrow", method=RequestMethod.POST)
public BorrowDTO borrow(#RequestBody BorrowDTO borrow ) {
borrowerService.borrowBook(borrow);
return borrow;
}
}
Edit: I forgot to mention that I use #RestController annotation on my controller class. It automatically includes #ResponseBody.
For security reasons some of the browsers don't allow to make ajax request that are not in the same origin. If you want read more on CORS I'm recommending this article. You have several choices to resolve your problem.
The first one is if you want to keep your current projects structure, both projects
to exists on different servers. You should enable CORS (Cross origin resource sharing) support on server side and I see that you are using newer Spring Framework so you can just add one
annotation that will enable CORS support. The annotation is #CrossOrigin see this article.
#RestController
#CrossOrigin(maxAge = 3600)
public class BookRestPostController {
#RequestMapping(value = "/borrow", method=RequestMethod.POST)
public BorrowDTO borrow(#RequestBody BorrowDTO borrow ) {
borrowerService.borrowBook(borrow);
return borrow;
}
}
If you are using older version of Spring Framework you can do in the manual way - add Filter which will add needed response headers. See this article
#Component
public class SimpleCORSFilter implements Filter {
#Override
public void init(FilterConfig arg0) throws ServletException {}
#Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
HttpServletResponse response=(HttpServletResponse) resp;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
chain.doFilter(req, resp);
}
#Override
public void destroy() {}
}
This is probably the cleanest way to achieve your goal.
Other two alternatives are to create server side proxy or to deploy both client and server side code in one server but my suggestion is not use proxy or same server !.
With simple words the browser will preflight request, it will send before every request, one extra request of type OPTIONS with specific headers (Access-Control-Request-Method and Access-Control-Request-Headers). These request headers will ask the server for permissions to make the actual request. Your preflight response needs to acknowledge these headers in order for the actual request to work.
Damn annoying bugOrFeature...
add #ResponseBody annotation
public #ResponseBody BorrowDTO borrow

Resources