2

I have an Angular app running which uses an external api to get countries ISOs. This API uses https and it's giving me an error.

The thing is: when I use a proxy in my angular local environment, mapping /iso-api/ to the real url it works ok.

"/iso-api/*": {
    "target": "https://www...",
    "pathRewrite": { "^/iso-api": "" },
    "secure": false,
    "changeOrigin": true,
    "logLevel": "debug"
}

But I want this to work in production, so I want to use the real url.

In my server I am returning the Access-Control-Allow-Origin: * header already.

I've tried to run the angular server with ssl (as the external api uses https), but I receive the same error.

I know a solution would be to implement the proxy in the server, but I believe this should not be done and there may be a way to retrieve this data from the frontend. Help please.

Response

This is the network error in Chrome: browser network tab

In Firefox, the request ends with 200 OK and returns data, but CORS error is thrown and I cannot access the data from the app: CORS header 'Access-Control-Allow-Origin' missing

General

Request URL: https://www...
Referrer Policy: no-referrer-when-downgrade

Request headers

:method: GET
:scheme: https
accept: application/json, text/plain, */*
accept-encoding: gzip, deflate, br
accept-language: es-ES,es;q=0.9,en;q=0.8
origin: http://localhost:4200
referer: http://localhost:4200/app/login
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: cross-site

Response headers

accept-ranges: bytes
cache-control: max-age=0
content-encoding: gzip
content-language: en-US
content-length: 68356
content-type: application/json
date: Mon, 27 Apr 2020 14:49:30 GMT
expires: Mon, 27 Apr 2020 14:49:30 GMT
referrer-policy: strict-origin-when-cross-origin
server-timing: cdn-cache; desc=HIT
server-timing: edge; dur=1
server-timing: ACTT;dur=0,ACRTT;dur=88
set-cookie: ... expires=Mon, 27 Apr 2020 16:49:30 GMT; max-age=7200; path=/; domain=...; HttpOnly
set-cookie: ... Domain=...; Path=/; Expires=Mon, 27 Apr 2020 18:49:30 GMT; Max-Age=14400; HttpOnly
set-cookie: ... Domain=...; Path=/; Expires=Tue, 27 Apr 2021 14:49:30 GMT; Max-Age=31536000; Secure
status: 200
vary: Accept-Encoding

UPDATE

Angular service code

import { HttpClient } from '@angular/common/http';

...

constructor(
    private _http: HttpClient,
    private _errorUtil: ErrorUtilService,
    private _converter: StoreConverter
  ) {}

...
  getCountries(): Observable<CountryWithLanguages[]> {
    return this._http.get<GetStoresResponse>(API.storeUrl).pipe(
      catchError(this._errorUtil.handle),
      map(result => result.stores),
      switchMap(stores => stores),
      filter(this._isActiveStore),
      map(store => this._converter.toView(store)),
      toArray()
    );
  }

To serve the app I use angular dev server, I do not add the 'Access-Control-Allow-Origin' header manually but, in the browser, I see that it is being added.

Chrome Network tab

angular.json

"serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "push-web-app:build",
            "proxyConfig": "src/proxy-local.conf.json"
          },
         }
adrisons
  • 3,443
  • 3
  • 32
  • 48
  • So you are calling your production API from your localhost server? It's a bit weird to have a err_failed error but still have response headers.. you have no other errors in the console? – David Apr 29 '20 at 19:01
  • So potentially dumb ask but have you tried using fetch or ajax? Just to see if you can pinpoint the exact location of the issue? – tuckerjt07 Apr 30 '20 at 03:21
  • It's an open API, not a production app that I manage, it works when accessing to the url from the browser. No errors in the console, just that message in the network tab... I've tried using fetch but the same happens. I think the problem is not with the http get request, but with the browser restricting cors – adrisons Apr 30 '20 at 11:33
  • The weird thing is that for CORS error you normally have a clear error message. Are you using chrome? You did not filter out warnings in the console? – David Apr 30 '20 at 13:42
  • Yes, I'm using Chrome but the console is pristine – adrisons Apr 30 '20 at 18:06
  • If the console isn’t showing any errors, then you can rule out CORS. – sideshowbarker May 02 '20 at 02:24
  • 2
    You might want to try using Firefox devtools to inspect the request — because there are unfortunately a number of different cases in which Chrome devtools is no longer exposing certain request and response details. See https://stackoverflow.com/q/57410051/441757 for example. – sideshowbarker May 02 '20 at 02:27
  • Thanks @sideshowbarker, in Firefox the CORS error is thown, I've updated the question – adrisons May 04 '20 at 11:33
  • I assume you are using the built-in `httpClient`? May we see the code for the HTTP call as well as how you construct the headers please? – Cliff Crerar May 04 '20 at 17:14
  • Can you show how you send the `Access-Control-Allow-Origin` header server side? The error clearly says that this header is missing – David May 05 '20 at 07:14
  • I've updated the question with the code you requested :) – adrisons May 05 '20 at 10:09
  • Did you check out [Access-Control-Expose-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers)? This header enables other CORS header to be present in response. You can check it like this: `this.http.get(url, { headers: { 'Access-Control-Expose-Headers': 'Access-Control-Allow-Origin' }});` – Raz Ronen May 05 '20 at 14:11
  • Are you using ExpressJS for your API? – ADAMJR May 06 '20 at 08:03
  • I don't really get what you are trying to do. You said 'In my server I am returning the Access-Control-Allow-Origin: * header already.', then in your update you said you were not. Are you trying to use angular's dev proxy in production mode? This is not a good idea. So the question is: do you have access to the server's API code? You need to set `Access-Control-Allow-Origin: *` (or restrict it to your domain) server side if you want to get rid of that CORS error. Or create your own proxy – David May 06 '20 at 08:56
  • Just to be clear: I don't own the server and I can't add any headers from it. I am using angular dev server in local dev, the app will be deployed in a remote server. Access-Control-Expose-Headers didn't work. I'm not using express, just angular dev server. @David proxy is a solution for local dev, but not for prod, that's the reason of my question. Thank you – adrisons May 06 '20 at 09:07
  • Maybe what I ask can't be done without a custom server with a proxy – adrisons May 06 '20 at 09:18
  • 1
    So the answer to your question is that you need to set up your own proxy in your production environment on your remote server. This is the only way to get around CORS if you do not own the API server. What kind of webserver are you using? Nginx? – David May 06 '20 at 09:40
  • Ok, thanks @David. I know how to proxy from the server, just wanted to know if there was some workaround from the frontend. – adrisons May 07 '20 at 08:34

4 Answers4

2

You can't request a resource from another domain. This would be a security hole. You can read more here: Same-origin policy

Sending Access-Control-Allow-Origin: * from your server won't give you access to the aforementioned API. The provider of this API needs to give you permission to access the API, you can't give yourself this permission.

The error you posted states that the Access-Control-Allow-Origin header is missing. It means that the API isn't sending this header.

There might be two reasons why this API isn't sending Access-Control-Allow-Origin header.

  1. A misconfiguration on the side of this API. In this case you have to ask the provider of this API to fix this issue.
  2. The API provider is restricting access to the API on purpose. In this case you have to ask the provider of this API to give you access from your domain.

You can also proxy the request through your server. The core difference when using proxy is that your server reads the resource from the API and not the client browser. See David's response on how to configure proxy with nginx.

Jakub Nowak
  • 316
  • 2
  • 10
1

You cannot bypass CORS browser side. If you are not able to modify the server side, your only solution is to use a proxy.

For development purposes, you can use angular's built-in proxy server, but not for production.

Here is a basic nginx config to do this

server {
    listen          80;
    server_name     yourdomain.com; #domain where your angular code is deployed

    location /iso-api{

        RewriteRule                     ^/iso-api/(.*)$ /$1 break;
        proxy_pass                      https://thirdpartyapidomain.com; #url of the API you are trying to access
    }

    location
    {
        #Your normal angular location
        #try_files ...
    }
}

This will redirects requests like

http://yourdomain.com/iso-api/countriesList to https://thirdpartyapidomain.com/countriesList;

Since now client and server API calls are on the same domain, you should not have CORS issues

David
  • 33,444
  • 11
  • 80
  • 118
  • To be exact you should say "You cannot bypass same-origin policy browser side" instead of "You cannot bypass CORS browser side". CORS is as method of relaxing same-origin policy. – Jakub Nowak May 06 '20 at 17:43
0

Use this site to resolve the CORS error: https://cors-anywhere.herokuapp.com/

Use Exemple https://cors-anywhere.herokuapp.com/https://freegeoip.app/json

this._http.get('https://cors-anywhere.herokuapp.com/https://freegeoip.app/json')

It's just a workaround but it works. Use it even just to understand if the error is related to CORS or something else.

Luciano
  • 450
  • 1
  • 7
  • 17
  • Thanks, but I need a strong solution. I am trying to find a solution in the frontend. As I wrote in the question: I know a solution would be to implement the proxy in the server, but I believe this should not be done and there may be a way to retrieve this data from the frontend. – adrisons May 06 '20 at 09:17
0

Try the .get call with option withCredentials: true:

return this._http.get<GetStoresResponse>(API.storeUrl, { withCredentials: true }).pipe();

...and/or making sure your browsers are up to date.

kflo411
  • 94
  • 5
  • The request does not need authorization. I've tried it anyway and it didn't work. – adrisons May 06 '20 at 09:15
  • I figured, but there's been some precedent in other cases where a required cert was passed via _withCredentials_ -- was worth a shot. Good luck! – kflo411 May 06 '20 at 13:30