401 Unauthorised due to CORS when sending request to get user token

Recently I start implementing a token based security system with angularjs and spring mvc. The idea is the following: 1. Visit /user/authenticate to get a security token and save the token to local storage 2. For each request sent by the angularJS client, use an interceptor to inject a X-Auth-Token header to the request.

In my spring back-end I have implemented an AuthenticationTokenProcessingFilter and a CustomAuthenticationEntryPoint. The first for extracting the token from the header and check if it is valid and the second to return a 401 unauthorized status when a request is not authenticated.

Please find some details about my back end code

AuthenticationController.java

@RestController
@RequestMapping(value="user")
public class AuthenticationController {

    @RequestMapping(value="authenticate", method = RequestMethod.POST)
    public ResponseEntity<?> login(@RequestParam("email") String email, 
        @RequestParam("password") String password) {
             //Check if user is valid and return token
        }
}

SecurityConfig.java

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
UsersRepository usersRepo;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {...}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .addFilterBefore(
                    new AuthenticationTokenProcessingFilter(usersRepo),
                    UsernamePasswordAuthenticationFilter.class)
        .addFilterBefore(this.corsFilter(), UsernamePasswordAuthenticationFilter.class)
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
            .csrf().disable().exceptionHandling()
        .and()
            .httpBasic()
            .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
        .and()
            .authorizeRequests()
            .antMatchers(HttpMethod.POST, "/user/authenticate").permitAll()
            .antMatchers("/**").authenticated()
            .anyRequest().authenticated();
}

@Bean
public CORSFilter corsFilter() {
    return new CORSFilter();
}
}

CORSFilter.java

public class CORSFilter implements Filter {

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
    HttpServletResponse response = (HttpServletResponse) res;
    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With, Origin, X-Auth-Token");
    response.addHeader("Access-Control-Expose-Headers", "X-Auth-Token");
    chain.doFilter(req, res);
}
}

Now I am using the following angularjs code in order to query the /user/authenticate endpoint which is not behind firewall

return $http.post(baseUrl + 'user/authenticate', 'email='+username+'&password='+password,
      {
        headers : {
          'content-type' : 'application/x-www-form-urlencoded'
        }
      }
  );

When I use the above code everything works. However If I remove the headers parameter from the request my angularjs client sends an OPTION request (rather than a POST request – I imagine this is related to my CORS filter) and my back end sends a 401 Unauthorised response.

Could you please give me a few more details why this is happening?

Thank you in advance!

Answer

I think I had a similar (the same?) problem and I tweaked my filter chain in WebSecurityConfigurerAdapter::configure(HttpSecurity http) a little bit to solve it.

I think what happens in your case is that the browser sends an OPTIONS request to figure out whether CORS is allowed or not by your server. The OPTIONS request is handled in your filter chain and will eventually match

.antMatchers("/**").authenticated()

Now you’ll probably end up in your CustomAuthenticationEntryPoint() and return a 401.

You could add:

.antMatchers(HttpMethod.OPTIONS, "/user/authenticate/").permitAll()

However, I think you still would get problems afterwards. When the client now wants to access other resources the token has to be sent in the header. Most likely this will result in another OPTIONS request which does not contain your token and so you will eventually end up with the same problem.

So what I did was to explicitly allow all OPTIONS request without checking for authentication.

.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

Now you bypass your security filter and you won’t return a 401. I think that this does not affect the security of the application as long as you won’t handle OPTIONS requests by yourself in a controller and allow someone to access critical data.