Reputation: 3311
I'm very confused by an error we are getting with a new Angular6 application. We have an existing API written in ASP.NET Core 2 and we have been consuming this API without issue from a chrome extension written in Typescript which posts data like so:
let login: any = {
username: options.userName,
password: options.password
};
let body: string = JSON.stringify(login);
return fetch(`${environment.jwtApiUrl}/api/token`,
{
method: "POST",
headers: {
'Accept': 'application/json',
"Content-Type": "application/json"
},
body: body
});
We are now consuming the exact same API from our Ionic 4 Angular application like so:
export class AuthenticationService {
constructor(
private http: HttpClient,
private storage: Storage) { }
public isLoggedIn(): boolean {
return this.storage.get("token") != null;
}
public async login(username: string, password: string): Promise<boolean> {
let login: any = {
username: username,
password: password
};
let body: string = JSON.stringify(login);
var result = await this.http.post<any>(`${environment.jwtApiUrl}/api/token`, body).toPromise();
if (result && result.token) {
this.storage.set("token", result.token);
return true;
};
return false;
}
logout() {
// remove user from local storage to log user out
this.storage.remove('token');
}
}
Within the angular application we are using an HttpInterceptor:
export class JwtInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add authorization header with jwt token if available
console.log("JwtInterceptor");
let token = JSON.parse(localStorage.getItem('token'));
if (token) {
request = request.clone({
setHeaders: {
"Authorization": `Bearer ${token}`
}
});
}
if (!request.headers.has('Content-Type')) {
request = request.clone({ headers: request.headers.set('Content-Type', 'application/json') });
}
console.log(request);
return next.handle(request);
}
}
In Angular the POST becomes an OPTIONS request and we get a 404 not found and:
Failed to load https://oururl/api/token: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8100' is therefore not allowed access.
Why do we get this just from Angular and not from the chrome extension which is also doing cross domain calls and is there anyway to work around this other than enabling cors on the API?
Upvotes: 2
Views: 1777
Reputation: 57223
CORS is enforced by browsers only and thats why anything else will not hit CORS issues.
The preflight OPTIONS request is a request to check that your server accepts CORS requests before making the actual real POST request. If the OPTIONS requests finds that the server has the necessary CORS headers it will make the POST request. If not, it won't even make the POST request (why should it waste its time by making the request if it already knows from the OPTIONS request that it does not have permission from the server to do so).
There are 2 approaches to solve this:
(1) Add the relevant CORS header to your server response, ensuring your server accepts PUT POST DELETE GET OPTIONS (and possibly HEAD)
(2) Use a proxy to rewrite the URL. The browser makes a request to an endpoint on the same domain and port (thus avoiding CORS) and the server serving up your Angular app rewrites the request to a different domain and port. For the dev environment, you can start webpack to proxy requests in this manner with the --proxy-config
flag (Webpack is used by Angular CLI under the hood). Use the --proxy-config
flag in your npm start script. You will need to reproduce this config on your prod server.
For (2) on your dev server create a file called proxy.json as follows:
{
"/api": {
"target": "http://localhost:9000",
"secure": false
}
}
And start your Angular CLI dev server with:
ng serve --proxy-config ./proxy.json
This config basically says proxy any request starting with /api in the path to the target domain and port.
Like I said, you will need to repeat this on your prod server which depends on which server you are using in prod. You will also need to ensure all API calls start with /api in the path.
Upvotes: 2
Reputation: 1
I had the same issue, but I did not want to change the CORS setting in my server. I solve the problem while I was developing by creating an api bridge node.js app and running that in my server. Node has that ability to set up the CORS in the file (which is a server) then I used node to make the api request and pass that on to my angular app (during development ). Is either that or like the previous answer said enable CORS for you api (or sever).
Upvotes: 0
Reputation: 863
I don't think you will be able to work around without enabling CORS on the API or using a proxy.
One guess that your Chrome extension worked, is that maybe the extension does not have the same (security) restrictions from the browser.
Upvotes: 0