CORS Issue

  • CORS in My Case

With an angular 2 app in front-end which calls APIs provided by a RESTful webservice in backend, at beginning, I have CORS issue when calling HTTP GET method. I simply solved it by adding @CrossOrigin annotaion to REST controller, e.g.,

@CrossOrigin(origins = "http://localhost:3000")

Later, when the app calls HTTP POST or PUT, it gets same CORS error with response code 403 and return invalid CORS request.

The differences here are POST, PUT method send OPTION request first, and then, send POST or PUT request again. This procedure works fine in local dev enviroment without CORS error. However, when I deployed the app to server, it dosen’t work like that any more, I notice that only POST or PUT request sent instead of sending OPTION request first.

Some explanation here…

In fact, there is nothing to do in Angular2 regarding cross domain requests. CORS is something natively supported by browsers. This link could help you to understand how it works:

http://restlet.com/blog/2015/12/15/understanding-and-using-cors/
http://restlet.com/blog/2016/09/27/how-to-fix-cors-problems/

To be short, in the case of cross domain request, the browser automatically adds an Origin header in the request. There are two cases:

Simple requests. This use case applies if we use HTTP GET, HEAD and POST methods. In the case of POST methods, only content types with the following values are supported: text/plain, application/x-www-form-urlencoded and multipart/form-data.

Preflighted requests. When the “simple requests” use case doesn’t apply, a first request (with the HTTP OPTIONS method) is made to check what can be done in the context of cross-domain requests.

So in fact most of work must be done on the server side to return the CORS headers. The main one is the Access-Control-Allow-Origin one.

Regarding Angular2, simply use the Http object like any other requests (same domain for example):

return this.http.get('https://angular2.apispark.net/v1/companies/')
.map(res => res.json()).subscribe(
...
);

According to MDN

Preflighted requests

Unlike simple requests (discussed above), “preflighted” requests first send an HTTP OPTIONS request header to the resource on the other domain, in order to determine whether the actual request is safe to send. Cross-site requests are preflighted like this since they may have implications to user data. In particular, a request is preflighted if:

It uses methods other than GET or POST. Also, if POST is used to send request data with a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain, e.g. if the POST request sends an XML payload to the server using application/xml or text/xml, then the request is preflighted.
It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)

Method works in my case
Apply intercepter and add http response headers.

public class ApiInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
}
return true;
}

Or simply use spring annotation

@CrossOrigin(origins = "*")

  • Other Methods

I also tried following methods without any luck.

  1. Apply JAVA configuration e.g.,
    @Bean
    public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurerAdapter() {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")
    .allowedOrigins("http://localhost:3000")
    .allowedMethods("GET", "POST", "PUT", "DELETE")
    .allowCredentials(false).maxAge(3600);
    }
    };
    }

But enabling CORS for the whole application doesn’t work for me.

  1. Enable CORS filter in Tomcat
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.exposed.headers</param-name>
<param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
</init-param>
</filter>
  1. Add headers at front-end
private headers:Headers = new Headers({
'Content-Type': 'application/json',
'Access-Control-Allow-Origin' : '*', // Required for CORS support to work
'Access-Control-Allow-Credentials' : true, // Required for cookies, authorization headers with HTTPS
Accept: 'application/json'
});

get(url:string, path:string):Observable<any> {
this.validateSession();
url = url == '' ? this.api_url : url;
return this.http.get(
`${url}${path}`, {
headers: this.headers, search: this.urlParams
})
.map(this.getJson)
.catch(this.handleError);
}