Venciendo al CORS en Ionic 4 / Angular 7

Ionic 4 / Angular 7 

El CORS es uno de los problemas más molestos a los que te enfrentas cuando haces una app híbrida en Ionic 4 / Angular 7, pero ojo, solo si envías cabeceras en las peticiones http, por ejemplo de autenticación.
Si no las envías, no pasa nada. Pero claro, es mucho más seguro enviar una autenticación en cada petición. Cuando lo haces, tendrás el famoso mensaje:

XMLHttpRequest cannot load https://api.example.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8100' is therefore not allowed access.
Es más, si logras resolverlo al probar la aplicación en el movil, igual no te funciona probando la aplicación con "ionic serve", o viceversa. Vamos a tratar aquí todos los pasos a seguir para que ese error no salga en ningún caso.

1. ionic-native/http

Debemos instalar este plugin https://ionicframework.com/docs/native/http y hacer con él todas las peticiones http.

2. Interceptor

Es básico que las cabeceras en las peticiones http las inyectemos con un interceptor. Es una clase estandard:
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse } from '@angular/common/http';
import { Observable, from } from 'rxjs';
import { Platform } from '@ionic/angular';
import { HTTP } from '@ionic-native/http/ngx';

type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' | 'upload' | 'download';

@Injectable()
export class HttpConfigInterceptor implements HttpInterceptor {
private username = "usuario";
private password = "contrasena";
constructor(
private nativeHttp: HTTP,
private platform: Platform,
) { }

public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token: string = btoa(this.username + ":" + this.password);
if (token) {
request = request.clone({ headers: request.headers.set('Authorization', 'Basic ' + token) });
}

if (!request.headers.has('Content-Type')) {
request = request.clone({ headers: request.headers.set('Content-Type', 'application/x-www-form-urlencoded') }); //application/json
}

request = request.clone({ headers: request.headers.set('Accept', '*/*') }); //application/json
request = request.clone({ headers: request.headers.set('Access-Control-Allow-Headers', 'Authorization, Expires, Pragma, DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range') });


if (!this.platform.is('cordova')) { return next.handle(request); }

return from(this.handleNativeRequest(request));
}

private async handleNativeRequest(request: HttpRequest<any>): Promise<HttpResponse<any>> {
const headerKeys = request.headers.keys();
const headers = {};

headerKeys.forEach((key) => {
headers[key] = request.headers.get(key);
});

try {
await this.platform.ready();

const method = <HttpMethod> request.method.toLowerCase();

const nativeHttpResponse = await this.nativeHttp.sendRequest(request.url, {
method: method,
data: request.body,
headers: headers,
serializer: 'json',
});

let body;

try {
body = JSON.parse(nativeHttpResponse.data);
} catch (error) {
body = { response: nativeHttpResponse.data };
}

const response = new HttpResponse({
body: body,
status: nativeHttpResponse.status,
headers: request.headers, //nativeHttpResponse.headers,
url: nativeHttpResponse.url,
});

return Promise.resolve(response);
} catch (error) {
if (!error.status) { return Promise.reject(error); }

const response = new HttpResponse({
body: JSON.parse(error.error),
status: error.status,
headers: error.headers,
url: error.url,
});

return Promise.reject(response);
}
}
}

Tanto la clase http como la clase del interceptor las declararemos en el app.module.ts. Además, en la sección "providers" de este fichero, pondremos:

{ provide: HTTP_INTERCEPTORS, useClass: HttpConfigInterceptor, multi: true },
donde "HttpConfigInterceptor" es el nombre de la clase del interceptor.

3. Proxy

Para poder usar "ionic serve", debemos usar un proxy. Solo hay que hacer dos pequeños cambios, que revertiremos a la hora de testear la app en el dispositivo real.
Lo primero creamos un fichero llamado proxy.conf.json en el raiz del proyecto:
{
"/api": {
"target": "https://direccion-de-nuestro-servicio-api-rest/api",
"changeOrigin": true,
"secure": false,
"logLevel": "debug"
}
}
el primer cambio es incluir este fichero en el angular.json, hacia la línea 74:
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "app:build",
"proxyConfig": "proxy.conf.json"
},
"configurations": {
"production": {
"browserTarget": "app:build:production"
},
"ci": {
"progress": false
}
}
}
y el segundo cambio es utilizar sólo el sufijo que hemos configurado en el proxy.conf.json como ruta raíz de las peticiones http.

Si antes como ruta raiz usabamos:
https://direccion-de-nuestro-servicio-api-rest/api

ahora usaremos:
/api

Y con ello nos funcionará en el "ionic serve". Estos dos cambios los desharemos para usarla en el dispositivo.
3 comments
  • Veronica
    01-08-2020 02:23
    Hola, por favor indicame que estoy haciendo mal, cuando incluyo el punto 2. Interceptor en el archivo app.module.ts no reconoce HTTP_INTERCEPTORS. Gracias
  • Enrique González
    01-08-2020 09:52
    En el app.module.ts debes incluirlo: import { HttpClient,HttpClientModule,HTTP_INTERCEPTORS } from '@angular/common/http';
  • Oscar Awad
    03-11-2020 15:08
    En un servicio definido como consumo yo el servicio HttpConfigInterceptor o sea como lo invoco para que me devuelva un get a una api. o se hace igual como en angular/ionic. Gracias..
Leave a comment
I have read and accept the Privacy Policy

The comments sent by each user will always be visible in the corresponding post, and will not be used for any other purpose. The user has the right to access, rectify and suppress said comments, as reflected in our Privacy Policy