1
votes

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?

2

2 Answers

2
votes

Just import HttpClientXsrfModule to your project, it will take care of reading the cookie and resending it as a custom header in every request.

The cookie and header names are not a standard, but rather a convention, so you can configure them if the default ones don't match your backend's ones.

As it happens, Django's cookie name and header name don't match Angular default ones so HttpClientXsrfModule has to be imported withOptions like this:

import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';

@NgModule({
    ...
    imports:[..., HttpClientXsrfModule.withOptions({ cookieName: 'csrftoken', headerName: 'X-CSRFToken' }), ...]
    ...
})
0
votes

try with this in your module instead of csrf headers

    @NgModule({ 
    providers: [ HttpXsrfInterceptor,
    { provide: HTTP_INTERCEPTORS, useExisting: HttpXsrfInterceptor, multi: true },
    { provide: HttpXsrfTokenExtractor, useClass: HttpXsrfCookieExtractor }, 
    { provide: XSRF_COOKIE_NAME, useValue: 'XSRF-TOKEN' }, 
    { provide: XSRF_HEADER_NAME, useValue: 'X-XSRF-TOKEN' }, ] }) 

replace the 'xsrf-token' and 'x-xsrf-token' with the value django sends back.

from: https://angular.io/api/common/http/HttpClientXsrfModule