szerhusenBC/jwt-spring-security-demo

Response for preflight has invalid HTTP status code 401

Closed this issue ยท 17 comments

I have created angular 2 client
But i receive this error when i call /user method

Response for preflight has invalid HTTP status code 401

bfwg commented

Are you running your Angular 2 client on a dev-server? Like is it running on a different port(4200)
?

Yes i do
I have tried with setting up CorsFilter, as explained in official spring guide to cors, but i still receive the message
What is interesting is that i log in and then receive this message when i call /user method

bfwg commented

Sounds to me like the request header from Angular 2 is wrong. Have you set withCredentials: true?

  headers = new Headers({
    'Accept': 'application/json'
  });

...

this.http.get(
  path,
  {
    headers: this.headers,
    withCredentials: true
  }
)
.map(this.extractData)
.catch(this.handleError);

Still the same problem

Here is my code

Spring Cors Filter:
@component
@order(Ordered.HIGHEST_PRECEDENCE)

public class SimpleCORSFilter implements Filter {

@Override
public void init(FilterConfig fc) throws ServletException {
}

@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
		throws IOException, ServletException {
	HttpServletResponse response = (HttpServletResponse) resp;
	HttpServletRequest request = (HttpServletRequest) req;
	response.setHeader("Access-Control-Allow-Origin", "http://localhost:4200");
	response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
	response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Credentials", "true");
	response.setHeader("Access-Control-Allow-Headers",
			"x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN");

	if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
		response.setStatus(HttpServletResponse.SC_OK);
	} else {
		chain.doFilter(req, resp);
	}

}

@Override
public void destroy() {
}

}

Angular code
Login service:

sendCredential(model) {

let tokenUrl1 = "http://localhost:8080/auth";
let headers1 = new Headers({ 'Content-Type': 'application/json' });
return this.http.post(tokenUrl1, JSON.stringify(model), { headers: headers1 });

}

sendToken(token) {
let tokenUrl2 = "http://localhost:8080/user";

let headers = new Headers({ 'Accept': 'application/json' });
headers.append('Authorization', 'Bearer ' + token);

return this.http.get(tokenUrl2, { headers, withCredentials: true });

}

Login component

onSubmit() {
this.loginService.sendCredential(this.model)
.map(res => res.json())
.subscribe(
data => {

    localStorage.setItem("token", data.token);
    localStorage.setItem("currentUserName", this.model.username);
    
    this.loginService.sendToken(localStorage.getItem("token")).subscribe(
      data => {
        this.currentUserName = this.model.username;
        localStorage.setItem("currentUserName", this.model.username);
        this.model.username = '';
        this.model.password = '';
      },
      error => console.log(error)
    );
    
  },
  error => console.log(error)
);

}

bfwg commented

@igoravramovic
I see, you should remove the Bearer. Like this: headers.append('Authorization', token);.
If you really want to use Bearer you can do something like this:

        if ( authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }

More detail: https://github.com/bfwg/springboot-jwt-starter/blob/master/src/main/java/com/bfwg/security/auth/TokenAuthenticationFilter.java#L52

@bfwg You're right, I didn't include the "Bearer" prefix, yet. But I've opened a ticket for that.

@igoravramovic could you fix your problem?

Sorry for not responding more quickly, i was not able to take time to try proposed solution
No, this didn't solve my problem i still receive same error message

@igoravramovic

I was having the same issue with the prefight OPTIONS request return a 401. When the OPTIONS request is sent from angular 2, it's sent without the Authorization header. Spring Security tries to authenticate the request without the header and returns a 401. In the JwtAuthenticationTokenFilter, the token is null.

I added a filter before the JwtAuthenticationTokenFilter filter to check for the OPTIONS request and return HttpServletResponse.SC_OK. Here is the filter that I have in my code.

public class CorsFilter extends OncePerRequestFilter {

    static final String ORIGIN = "Origin";

    protected void doFilterInternal(
        HttpServletRequest request, 
        HttpServletResponse response, 
        FilterChain filterChain) throws ServletException, IOException {
    
        String origin = request.getHeader(ORIGIN);
    
        response.setHeader("Access-Control-Allow-Origin", "*");//* or origin as u prefer
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "PUT, POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "content-type, authorization");
    
        if (request.getMethod().equals("OPTIONS"))
            response.setStatus(HttpServletResponse.SC_OK);
        else 
            filterChain.doFilter(request, response);
    
    }
}

In my security config I added the bean for the filter and added it to the configuration before JwtAuthenticationTokenFilter.

@Bean
public CorsFilter corsFilter() throws Exception {
    return new CorsFilter();
}


http
    .addFilterBefore(corsFilter(), UsernamePasswordAuthenticationFilter.class)
    .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
    .headers()
    .cacheControl();

This seems to be working for me. Hope this helps you.

@igoravramovic is this helping you?

@jmw5598 - I'm not sure if this can help, but try putting

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}

On your securityConfig.java class. As this ignores and let http:options request pass through.

@jrcastillo you are absolutely right, and this should solve OP's problem except he has to remove the Bearer part. To understand the original problem, please follow this link https://stackoverflow.com/questions/38368794/angular-2-basic-authentication-not-working

@igoravramovic is not replying since 19th Apr so I close this ticket now.

You can get through this very easy! Let's follow me right now

  1. Create a shortcut on your desktop
  2. Right-click on the shortcut and click Properties
  3. Edit the Target property
  4. Set it to "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir="C:/ChromeDevSession"
  5. In VIsual Studio Code, run ionic serve -l
  6. You're gonna see new tab open http://localhost:8100/ionic-lab. You should be aware that this link is opened in the normal chrome tab, not the "disable-web-security" chrome we have set up.
  7. Double click to the shortcut that we have set up to open the "disable-web-security" chrome tab. Then paste http://localhost:8100/ionic-lab into this tab.

So the reason that we get multiple errors when working with woo-commerce-api is this "web-security" by Google. Then you just disable it and you actually don't need any CORS Extensions. So remove them right now if you have installed.

And this solution i write for people who learn this course https://www.udemy.com/ionic-3-apps-for-woocommerce-build-an-ecommerce-mobile-app/. This is an ionic e-commerce app that using woo-commerce-api to set and get data from Wordpress (local or live server). If you have trouble in other language not ionic, it still works fine.

Actually i have done a lot of searchings on Google to find this solution. I hope this helps all of you. Now, i need to go to bed because tomorrow i have a final report about this ionic project with my lecturer ๐Ÿ˜ƒ

See ya!
Quy Le

@jrcastillo thanks for the tip

And if anyone is using HttpSecurity then we need to use this

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()//allow CORS option calls
            .antMatchers("/resources/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .httpBasic();
}

I can not for the life of me get cors to work on this project. I tried all of the above & nothing works. The only change I've been able to get working is applying a filter which only works after the user is already logged in showing the new headers on the response, but still fails a preflight request.

POST http://localhost:8080/auth HTTP/1.1
Content-Type: application/json; charset=utf-8

{
    "username":"admin",
    "password":"admin"
}


GET http://localhost:8080/user HTTP/1.1
content-type: application/json; charset=utf-8
authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTUyMzk5NTUwNSwiaWF0IjoxNTIzMzkwNzA1fQ.ZJTUYWU4MVEIOR5EjLXqPiIsmBafATuSju6xSkBF8hmx6USM8q7qLXpCc4Wt2ZiIC3jKtSSFThP0sQfwp3xcRQ

update after trying a ton of things the solution for me ended up being the following:

package org.tci.filters;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class CorsConfig {

	@Bean
	public FilterRegistrationBean corsFilter() {
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		CorsConfiguration config = new CorsConfiguration();
		config.setAllowCredentials(true);
		config.addAllowedOrigin("*");
		config.addAllowedHeader("*");
		config.addAllowedMethod("OPTIONS");
		config.addAllowedMethod("HEAD");
		config.addAllowedMethod("GET");
		config.addAllowedMethod("PUT");
		config.addAllowedMethod("POST");
		config.addAllowedMethod("DELETE");
		config.addAllowedMethod("PATCH");
		source.registerCorsConfiguration("/**", config);
		// return new CorsFilter(source);
		final FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
		bean.setOrder(0);
		return bean;
	}

	@Bean
	public WebMvcConfigurer mvcConfigurer() {
		return new WebMvcConfigurerAdapter() {
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/**").allowedMethods("GET", "PUT", "POST", "GET", "OPTIONS");
			}
		};
	}
}

and adding

@CrossOrigin(origins = { "*" }, maxAge = 6000)

at the top of each controller