Solucionando el problema de CORS en Angular 🅰

Publicado el 15.10.2022 a las 17:56

Solucionando el problema de CORS en Angular 🅰

  1. ¿Qué es el error de CORS?

  2. ¿Por qué se produce el error de CORS?

  3. Cómo evitar el error de CORS teniendo acceso al código del backend

    • NodeJS

    • .NET 6

    • ASP.NET MVC

  4. ¿Cómo evitamos el error de CORS si no tenemos acceso al backend?

    • Creando el proxy

    • Carga el proxy

    • Configura dos entornos, de desarrollo y de producción

Logo de fjmduran

Solucionando el problema de CORS en Angular 🅰

Si estás en este artículo es porque te has encontrado con el típico error de CORS desarrollando en tu proyecto de Angular 🚩


Este error no es de Angular, viene provocado por hacer peticiones a un recuros (dominio, puerto...) desde otro recurso distinto desde el que generó la petición.

Desarrollador frustrado

Lo que te voy a explicar a continuación sólo es válido cuando estás desarrollando, si el error te está dando con el proyecto en producción tendrás que hablar con el administrador de tu backend para que permita, en el caso de que proceda, las peticiones desde orígenes distintos.


Después de probar diferentes soluciones como complementos del navegador que deshabilitaban las CORS... lo que mejor y siempre te funcionará es utilizar un proxy para redirigir el tráfico y así hacer que todas las peticiones se realizen al "mismo recurso".


Te explico 👇

¿Qué es el error de CORS?

El Intercambio de Recursos de Origen Cruzado (CORS (en-US)) es un mecanismo que utiliza cabeceras HTTP adicionales para permitir que un user agent (en-US) obtenga permiso para acceder a recursos seleccionados desde un servidor, en un origen distinto (dominio) al que pertenece. Un agente crea una petición HTTP de origen cruzado cuando solicita un recurso desde un dominio distinto, un protocolo o un puerto diferente al del documento que lo generó.


Para que lo entiendas, la política de CORS lo que hace es garantizar que todas las peticiones se realizan desde y hacia un mismo recurso (dominio, puerto, protocolo...).

Dicho de otra forma, que no se crucen peticiones entre recursos distintos.

Arquitectura de peticiones en una aplicación

¿Por qué se produce el error de CORS?

Normalmente, cuando estás desarrollando una aplicación tienes corriendo en tu servidor de desarrollo tanto el backend como el frontend, y cada unos de ellos está corriendo sobre un puerto.


Cuando tu aplicación de Angular (que corre por defecto en el puerto 4200) hace una petición a tu backend (que está corriendo en otro puerto) produce un error de CORS, ya que las peticiones se están haciendo desde recursos distintos (en este caso puertos distintos).

Típico error de CORS con Angular

Si no tienes el control sobre el código del backend, te recomiendo que uses un proxy para poder seguir desarrollando sin que te moleste el error de CORS.

Evitar el error de CORS teniendo acceso al código del backend

Lo ideal será tener acceso al código del backend, lo más práctico será que habilites una lista blanca de dominios permitidos, añadiendo tu dominio de desarrollo a dicha lista blanca.


Te pongo el ejemplo si tu backend estuviera hecho con NodeJS o con .NET

Node JS

const whiteList=['http://localhost:4200','...'];
app.use(cors({origin: whiteList}));

.NET

var builder = WebApplication.CreateBuilder(args);
//CORS
builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowedOrigins",
        policy =>
        {
            policy.WithOrigins("http://localhost:4200") // fíjate que el puerta está incluido
                .AllowAnyHeader()
                .AllowAnyMethod();
        });
});
var app = builder.Build();
app.UseCors("MyAllowedOrigins");

//A continuación las peticiones a las que quieres que responda tu API
app.MapGet("/", () =>
{
    string html = "<h1>Hola mundo</h1>";    
    return Results.Content(html,"text/html");
});

ASP.NET MVC

En el fichero Global.asax.cs debes de añadir el método Application_BeginRequest como sigue:

protected void Application_BeginRequest(object sender, EventArgs e)
{
    //bloque de código para eliminar los headers de server
    var app = sender as HttpApplication;
    if (app != null && app.Context != null)
    {
        app.Context.Response.Headers.Remove("Server");
    }
    //bloque de código para eliminar los headers de server

    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    {
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");

        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization");
        HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
        HttpContext.Current.Response.End();
    }
}

Una vez tengas tu aplicación de backend en producción deberías de eliminar el recurso de desarrollo de la lista blanca para evitar usos maliciosos 🏴‍☠️

¿Cómo evitamos el error de CORS si no tenemos acceso al backend?

Como ya he dicho, siempre que no tengas acceso al código que se ejecuta en el backend lo que puede hacer es configurar un proxy para tu aplicación de angular.


El Proxy básicamente intenta engañar al navegador, lo hace fingiendo tener la API en el mismo origen mientras que debajo del capó está accediendo al recurso desde otro origen.


De esta forma, el navegador podría creer que se está cumpliendo la política del mismo origen, eliminando así el error CORS.


¿Cómo lo logramos? Al turrón 👇

Creando el proxy

Imagina que tienes corriendo tu backend en tu puerto 3000.


Crea un fichero proxy.conf.json y copia el siguiente código.

            
  {
    "/api": {
      "target": "http://localhost:3000/v1",
      "secure": false,
      "changeOrigin": false,
      "pathRewrite": {
        "^/api": ""
      }
    }
  }
  
        

🚨 Aségurate de crear el fichero en la raíz del proyecto, es decir, en el mismo directorio donde está el index.html


Explicación de cada parámetro del fichero:

  • "/api" especifica la ruta para el proxy y el objeto anidado especifica la configuración. Por lo tanto, para realizar solicitudes, la URL será http://yourhost/api (para mi proyecto de Angular configurado por defecto, sería http: //localhost:4200/api)
  • "target" especifica la URL de la API a la que intenta acceder, en este caso, localhost:3000
  • "secure" se usa para especificar si el objetivo se está sirviendo a través de http o https. Actualmente está establecido en false porque el punto final se está sirviendo a través de http.
  • "changeOrigin" especifica que el destino está en otro dominio diferente al que se está ejecutando actualmente la aplicación. En este caso, se establece en false porque tanto el frontend como el backend se están usando en localhost, pero si estuvieras haciendo peticiones a un backend alojado en un dominio distinto esta propiedad sería true.
  • "pathRewrite" le permite modificar cómo la aplicación interactúa con el objetivo. Sin la propiedad "pathRewrite", cada llamada a la URL del proxy http://localhost:4200/api corresponderá a https://localhost:3000/api. Esto no será un problema si localhost:3000 tiene un punto final de API, pero imagina que en realidad no proporcionan dicho punto final, esto generaría un error. Por lo tanto, configurar el objeto anidado "pathRewrite" con "^/api": "" ayuda a eliminar la "api" no deseada al final de la URL. Esto, a su vez, garantiza que llamar a http://localhost:4200/api corresponderá a http://localhost:3000/v1

Carga el proxy

Cuando cargues tu aplicación de Angular en local corre el comando

ng serve --proxy-config proxy.conf.json

Configura dos entornos, de desarrollo y de producción

Para evitar olvidos a la hora de desplegar tu aplicación, configura en los distintos fichero de environment.ts y environment.prod.ts la url del backend.

//DESARROLLO
//environment.ts
export const environment = {  
  production: false,
  apiBackend:'http://localhost:3000'
};
//PARA PRODUCCIÓN
//environment.prod.ts
export const environment = {  
  production: true,
  apiBackend:'https://tudominio.com'
};

Hasta luego 🖖