Search code examples
djangoangularcorsdjango-cors-headers

Django+Angular CORS not working with POST


My Angular4 app (running on http://127.0.0.1:4200 development server) is supposed to access a django REST backend on the web. The backend is under my control and is available only via HTTPS (running Apache that tunnels the request to a gunicorn server running on an internal port). Let's say that this is https://example.com/. For historical reasons, logging the user in is done using sessions, because I want the users to be able to also use Django's admin interface after they logged in. The workflow is as follows:

  • Users opens http://127.0.0.1:4200, I perform a GET request to https://example.com/REST/is_logged_in which returns a 403 when the user isn't logged in via sessions yet, 200 otherwise. In the former case, the user is redirected to https://example.com/login/, rendered by Django's template engine, allowing the user to log in. Once logged in, the user is redirected to http://127.0.0.1:4200
  • When clicking on some button in my Angular UI, a POST request is performed. This post request fails with 403, even though the preflight OPTIONS request explicitly lists POST as allowed actions.

Here is my CORS configuration in Django:

NG_APP_ABSOLUTE_URL = 'http://127.0.0.1:4200'
# adapt Django's to Angular's presumed XSRF cookie/header names
CSRF_COOKIE_NAME = "XSRF-TOKEN"
CSRF_HEADER_NAME = "HTTP_X_XSRF_TOKEN"
CORS_ORIGIN_WHITELIST = (
    urlparse(NG_APP_ABSOLUTE_URL).netloc
)
CSRF_TRUSTED_ORIGINS = (
    urlparse(NG_APP_ABSOLUTE_URL).netloc
)
CORS_ALLOW_HEADERS = default_headers + (
    'x-xsrf-token',
)
CORS_ALLOW_CREDENTIALS = True

This is what Chrome reports for the (successful, 200) first REST GET request to check whether the user is logged in (after he successfully did) in the response:

Access-Control-Allow-Credentials:true Access-Control-Allow-Origin:http://127.0.0.1:4200 Allow:GET, HEAD, OPTIONS Connection:close Content-Type:application/json Date:Wed, 26 Apr 2017 15:09:26 GMT Server:gunicorn/19.6.0 Set-Cookie:XSRF-TOKEN=...; expires=Wed, 25-Apr-2018 15:09:26 GMT; Max-Age=31449600; Path=/ Transfer-Encoding:chunked Vary:Accept,Cookie,Origin X-Frame-Options:SAMEORIGIN

The corresponding request had this:

Cookie:sessionid=...; XSRF-TOKEN=... Host:example.com Origin:http://127.0.0.1:4200 Referer:http://127.0.0.1:4200/

Now, to the actual problem:

Preflight request:

Request URL:https://example.com/REST/change_user_data/
Request Method:OPTIONS
Status Code:200 OK
Access-Control-Request-Headers:content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:example.com
Origin:http://127.0.0.1:4200
Referer:http://127.0.0.1:4200/dashboard/account

Preflight response:

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with, x-xsrf-token
Access-Control-Allow-Methods:DELETE, GET, OPTIONS, PATCH, POST, PUT
Access-Control-Allow-Origin:http://127.0.0.1:4200
Access-Control-Max-Age:86400
Connection:close
Content-Length:0
Content-Type:text/html; charset=utf-8
Date:Wed, 26 Apr 2017 15:36:56 GMT
Server:gunicorn/19.6.0
Vary:Origin
X-Frame-Options:SAMEORIGIN

Now my failing (403) POST request:

Accept:application/json
Accept-Encoding:gzip, deflate, br
Accept-Language:de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
Connection:keep-alive
Content-Length:60
Content-Type:application/json
Cookie:sessionid=...; XSRF-TOKEN=...
Host:example.com
Origin:http://127.0.0.1:4200
Referer:http://127.0.0.1:4200/dashboard/account

The response headers:

HTTP/1.1 403 Forbidden
Date: Wed, 26 Apr 2017 15:36:56 GMT
Server: gunicorn/19.6.0
Vary: Accept,Cookie,Origin
X-Frame-Options: SAMEORIGIN
Content-Type: application/json
Access-Control-Allow-Credentials: true
Allow: POST, OPTIONS
Access-Control-Allow-Origin: http://127.0.0.1:4200
Set-Cookie: XSRF-TOKEN=...; expires=Wed, 25-Apr-2018 15:36:56 GMT; Max-Age=31449600; Path=/
Connection: close
Transfer-Encoding: chunked

Why wouldn't this request work? It makes little sense to me!

Best regards!


Solution

  • I had the same problem, trying to send a POST request to Django (port 8000) from my Angular CLI (port 4200). I thought it was a problem of Django so I installed cors package however the "problem" is with the browser (actually is not a problem, it is a security issue, see here). Anyway, I solved the problem adding a proxy rule for my Angular CLI, as follows:

    • First, instead of sending my requests to http://localhost:8000/api/... is send them to /api/ (i.e. to my ng server running at port 4200).
    • Then I added a file in my Angular project called "proxy.conf.json" with the following content:
    {
      "/api": {
        "target": "http://localhost:8000",
        "secure": false
      }
    }
    
    • Finally, run your ng server with the flag "--proxy-config": ng serve --watch --proxy-config proxy.conf.json

    All API requests will be sent to the port 4200 and Angular will internally redirect them to Django, avoiding the CORS problem. Note that this is only valid for development and won't be used when you build your app code and add it as the static code of your Django server.

    Finally, with this solution I didn't need anymore the python module for cors so you could remove it.