1
votes

I'm experiencing a CORS issue consuming a Django Rest Framework REST API from an Angular 6 app.

The API runs on http://localhost:55098/admin. It works fine when I invoke it with Insomnia. Angular app runs on http://localhost:4200.

When I first typed http://localhost:4200/cgestores it should redirect to http://localhost:55098/admin/cgestores, and it did, but I got a CORS error message (see below).

My configuration:

REST API: I'm using Python 3.7 (64 bit), Django (2.1.5) and Django Rest Framework (3.9.1)

My settings.py:

ALLOWED_HOSTS = [
    'testserver',
    'localhost'
    ]

CORS_ORIGIN_ALLOW_ALL = True

INSTALLED_APPS = [
    # Add your apps here to enable them
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework_swagger',
    'corsheaders',
    'admin_v20',
]

MIDDLEWARE_CLASSES = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Angular client: I'm using Angular 6.4.1. The service (apiusuarios.service.ts):

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})

export class ApiusuariosService {

  datos: iCentroGestor;

  constructor(private http: HttpClient) { }

  getMock(): any{
    return [
      {"gececo":"a", "gecdes":"one"},
      {"gececo":"b", "gecdes":"two"},
      {"gececo":"c", "gecdes":"three"}
    ];
  }

  getUsers(): Observable<any> {
    return this.http.get(endpoint + 'cgestores').pipe(map(this.extractData));
  }

}

export interface iCentroGestor {
  gececo: string;
  gecdes: string;
}

const endpoint = 'http://localhost:55098/admin/';

Method getMock() returns a fixed JSON value just for test purposes, and it works fine. The method that actually calls the API is getUsers(), and is the method that triggers the CORS error:

Access to XMLHttpRequest at 'http://localhost:55098/admin/cgestores/' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

(that's Chrome; Firefox and Edge return similar responses)

This seemed to be an issue for a lot of people, I checked these articles:

...and a few more, and also this video:

...and they all pretty much sayed the same: it's not an Angular problem, it's a server issue, and I should make the following modifications to my DRF project:

  1. Install CORS headers package

  2. Modify settings.py with the following:

    • Add CORS_ORIGIN_WHITELIST
    • Add corsheaders to INSTALLED_APPS
    • Add corsheaders.middleware.CorsMiddleware to MIDLEWARE_CLASSES

All done, but it still didn't work. After trying all that, I read that I could specify the header that I need on my response. So I tried this in my DRF project:

return Response(data=pDatos, status=pStatus, headers={"Access-Control-Allow-Origin":"*"})

... and bingo, it worked!

However, this is not really a solution, I don't like to have to depend on a specific parameter on a specific response. And, on top of that, I have no way to restrict who can access the site and who can't, which would be really easy to do with CORS_ORIGIN_WHITELIST (well, I could, but writing a parameter with a list of sites in place of the asterisk symbol doesn't seem to be the right option).

So, what am I doing wrong? Why do my settings.py options not work? I commented them out, and just left the return Response... and it still works, so settings.py is not really doing anything.

Sorry for this long post. Any ideas? Thanks in advance for your help.

2
If at some point you have flushed your database, you may need to create the CorsModel model table again: manage.py makemigrations corsheaders; manage.py migrate corsheaders.Rémi Héneault
I'm using Visual Studio 2017. I used the Make migrations... and Migrate context menu options to apply any pending changes, but it didn't detect any. Still not working.VMF

2 Answers

1
votes

Found the answer myself, I'd like to share it.

I'm using Django 2.1.5. It appears that section MIDDLEWARE_CLASS is simply called MIDDLEWARE since version 2. When I changed it I was forced to comment out django.contrib.auth.middleware.SessionAuthenticacionMiddleware, and that did the magic. Now everything works as it should.

1
votes

Start by installing django-cors-headers using pip

pip install django-cors-headers

You need to add it to your project settings.py file:

INSTALLED_APPS = (
    ##...
    'corsheaders'
)

Next you need to add corsheaders.middleware.CorsMiddleware middleware to the middleware classes in settings.py

MIDDLEWARE = (
    'corsheaders.middleware.CorsMiddleware',
    #...
)

You can then, either enable CORS for all domains by adding the following setting

CORS_ORIGIN_ALLOW_ALL = True

Or Only enable CORS for specified domains:

CORS_ORIGIN_ALLOW_ALL = False

CORS_ORIGIN_WHITELIST = (
    'http://localhost:8000', # Allowed host, if CORS_ORIGIN_ALLOW_ALL is False
)