I am trying to make a POST request from an Angular 6 app to a Django backend. Even though I include the csrf token in the headers, Django is logging a 403 Forbidden error as "CSRF token missing or incorrect". My code is as follows (with extraneous headers in my attempts to satisfy Django):
Angular Component:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { SharedService } from '../../shared.service';
import { CookieService } from 'ngx-cookie-service';
@Injectable({
providedIn: 'root'
})
export class EmailService {
// http options used for making any writing API calls with csrf token
private httpOptions: any;
csrfToken;
constructor(private http: HttpClient, private cookieService: CookieService) {
// Set the csrf token
this.http.get(SharedService.contactEmailUrl).subscribe((data) => (this.csrfToken = data['csrfToken']), (error1) => console.log(error1));
}
sendMailgunContactMessage(payload) {
// Configure CSRF token header options
this.httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': this.csrfToken,
'x-csrftoken': this.csrfToken,
'X-XSRF-TOKEN': this.csrfToken,
'XSRF-TOKEN': this.csrfToken,
'X-CSRF': this.csrfToken,
csrfmiddlewaretoken: this.csrfToken,
csrftoken: this.csrfToken
}),
withCredentials: true
};
let body = {
csrfmiddlewaretoken: this.csrfToken,
content: payload
};
return this.http.post(SharedService.contactEmailUrl, body, this.httpOptions);
}
}
Django Settings:
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'XYZ'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
CSRF_COOKIE_SECURE = False
CSRF_USE_SESSIONS = False
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
'XYZ'
)
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'X-CSRFToken',
'x-csrftoken',
'X-XSRF-TOKEN',
'XSRF-TOKEN',
'csrfmiddlewaretoken',
'csrftoken',
'X-CSRF'
)
CORS_ALLOW_CREDENTIALS = True
# Application definition
INSTALLED_APPS = (
'corsheaders',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
MIDDLEWARE_CLASSES = (
'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'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',
'django.middleware.security.SecurityMiddleware',
)
MIDDLEWARE = (
'django.middleware.csrf.CsrfViewMiddleware'
)
ROOT_URLCONF = 'django_project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'django_project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'debug.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
},
}
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.8/howto/static-files/
STATIC_URL = '/static/'
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
# Allow Django from all hosts. This snippet is installed from
# /var/lib/digitalocean/allow_hosts.py
import os
import netifaces
# Find out what the IP addresses are at run time
# This is necessary because otherwise Gunicorn will reject the connections
def ip_addresses():
ip_list = []
for interface in netifaces.interfaces():
addrs = netifaces.ifaddresses(interface)
for x in (netifaces.AF_INET, netifaces.AF_INET6):
if x in addrs:
ip_list.append(addrs[x][0]['addr'])
return ip_list
# Discover our IP address
ALLOWED_HOSTS = ip_addresses()
Django View:
from django.shortcuts import render
from django.http import HttpResponse
from .controllers import *
import json
from django.middleware.csrf import get_token
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.csrf import csrf_protect
# Create your views here.
@ensure_csrf_cookie
def email(request):
if request.method == 'POST':
json_body = json.loads(request.body)
response = HttpResponse(send_contact_message(json_body))
return response
elif request.method == 'GET':
csrf_token = get_token(request)
response = HttpResponse('{ "csrfToken": "' + csrf_token + '" }')
return response
Note: Django is not setting a csrftoken cookie in cookies, not sure if that matters or not.
Why is the CSRF token(s) that I send back in the headers not being verified by Django?