I have an issue regarding setting up Django and Vuejs in order to make them work together. My issue is regarding csrftoken and how this it is passed between Django and Vuejs.
Therefore, my setup is as follow:
Django is running on http://localhost:8000
Vuejs is running on http://localhost8080
I am using django rest framework and SessionAuthentication.
settings.py
"""
Django settings for core project.
Generated by 'django-admin startproject' using Django 3.1.2.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "8g+_gwm=s^kw69y3q55_p&n(_y^^fa!8a^(f7c)#d&md2617o9"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ["*"]
# Cors setup
# CORS_ORIGIN_ALLOW_ALL = False
# For CSRF
CORS_ALLOWED_ORIGINS = [
"http://localhost:8080",
"http://127.0.0.1:8080",
]
CSRF_TRUSTED_ORIGINS = [
"http://localhost:8080",
"http://127.0.0.1:8080",
]
# For Cookie
CORS_ORIGIN_WHITELIST = ("http://localhost:8080", "http://127.0.0.1:8080")
CORS_ALLOW_CREDENTIALS = True
# CSRF_USE_SESSIONS = False
CSRF_COOKIE_HTTPONLY = False
SESSION_COOKIE_SAMESITE = "None"
CSRF_COOKIE_SAMESITE = None
SESSION_COOKIE_HTTPONLY = False
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"corsheaders",
"blog.apps.BlogConfig",
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "core.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 = "core.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.1/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/3.1/howto/static-files/
STATIC_URL = "/static/"
# Django Rest Framework Authentication
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
}
serializers.py
class LoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
user = authenticate(username=attrs.get("username"), password=attrs.get("password"))
if not user:
raise serializers.ValidationError("Incorect email or password")
return {"user": user}
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "email"]
views.py
class LoginView(APIView):
permission_classes = [permissions.AllowAny]
def post(self, request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data.get("user")
login(request, user)
return Response(UserSerializer(user, context={"request": request}).data)
And this is the resource that I am trying to access:
views.py
class PostList(APIView):
""" Simple post view. """
def get(self, request):
post = Post.objects.all()
serializer = PostSerializer(post, many=True)
return Response(serializer.data)
def post(self, request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Whenever I try to access this from frontend, I get the following error: Status Code: 403 Forbidden
Login.vuejs
<template>
<div class="home">
<h1>{{ message }}</h1>
<h2>Posts</h2>
<ul>
<li v-for="post in posts" :key="post.id">
{{ post.title }}
</li>
</ul>
<p>
<button #click="getPosts">Get Posts</button>
<button #click="createPost">Create Post</button>
<button #click="login">Login</button>
</p>
</div>
</template>
<script>
// # is an alias to /src
import axios from "axios";
// import Cookies from "js-cookie";
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.xsrfHeaderName = "X-CSRFToken";
export default {
name: "Home",
data() {
return {
message: "",
posts: [],
};
},
methods: {
login: function() {
const baseUrl = "http://127.0.0.1:8000/rest-api/auth/";
axios
.post(baseUrl, { username: "madalin", password: "test" })
.then((response) => {
console.log(response);
});
},
createPost: function() {
const baseUrl = "http://127.0.0.1:8000/rest-api/posts/";
axios
.post(baseUrl, {
title: "Post title vuejs",
body: "This is a simple post body",
})
.then((response) => {
console.log(response);
});
},
getPosts: function() {
const baseUrl = "http://127.0.0.1:8000/rest-api/posts/";
axios
.get(baseUrl, {
withCredentials: true,
})
.then((response) => {
this.posts = response.data;
});
},
},
};
</script>
Now, whne I click on login, the response header is as follow:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://127.0.0.1:8080
Allow: POST, OPTIONS
Content-Length: 63
Content-Type: application/json
Date: Tue, 20 Oct 2020 22:22:51 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.5
Set-Cookie: csrftoken=6psCovwjDLVYzoZUDpiRDMjS6cQ6d8N9ubT3wNVjUZFAhmHinEFecYOHuWP2gC3J; expires=Tue, 19 Oct 2021 22:22:51 GMT; Max-Age=31449600; Path=/
Set-Cookie: sessionid=1txxggzki1r4s3cm0pc6faiityo36wep; expires=Tue, 03 Nov 2020 22:22:51 GMT; Max-Age=1209600; Path=/; SameSite=None
Vary: Accept, Cookie, Origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
As per my understanding, the csrftoken from response header should be send forward to Django with each new request. Request which is comming from axios.
Is there anything else that I can do/check?
I am struggling with this issue for the last 2 weeks at least, any help will be very appreciate.
Thank you,
I have managed to figure out what is the right setup in order to use SessionAuthentication with Django Rest Framework.
I will post the answer here maybe will help someone.
Step 1 - Make sure you have the following setup in your settings.py file
# For CORS and CSRF
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = [
"http://localhost:8080",
"http://127.0.0.1:8080",
"http://localhost:8000",
"http://127.0.0.1:8000",
]
CSRF_TRUSTED_ORIGINS = [
"http://localhost:8080",
"http://127.0.0.1:8080",
"http://localhost:8000",
"http://127.0.0.1:8000",
]
# For Cookie
CSRF_COOKIE_HTTPONLY = True
SESSION_COOKIE_HTTPONLY = True
# Since the frontend is on `http://localhost:8080` and the backend is on `http://localhost:8000`, they are two different origins and we need to set samesite to false
# UPDATE: Starting with django 3.1 this must be set to 'None'
SESSION_COOKIE_SAMESITE = 'None'
CSRF_COOKIE_SAMESITE = 'None'
# When you test this make sure both applications are on the same domain. Like `http://127.0.0.1:8000` and `http://127.0.0.1:8080`
Step 2 - Create the login and logout views
from django.contrib.auth import login, logout
from rest_framework import permissions, status
from rest_framework.response import Response
from rest_framework.views import APIView
class LoginView(APIView):
permission_classes = [permissions.AllowAny]
def post(self, request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data.get("user")
login(request, user)
return Response(UserSerializer(user, context={"request": request}).data)
class LogoutView(APIView):
permission_classes = [permissions.AllowAny]
def get(self, request):
logout(request=request)
return Response({"message": "You have been logout succesfully"})
Step 3 - Make sure that every request from frontend is made with credentials included
import axios from "axios";
const API = axios.create({
baseURL: `http://127.0.0.1:8000/rest-api/`,
headers: {
Accept: "application/json",
"Content-type": "application/json"
},
timeout: 10000,
withCredentials: true
});
export default {
login(payload) {
return API.post("auth/", payload);
},
logout() {
return API.get("logout/");
},
}
Related
I have created a vue and DRF application, which is working fine locally, it sets access, refresh and csrf token in cookies on login and so all of the isAuthenticated routes work fine.
But i handed it over to the person who's meant to deploy this on vps and now it doesn't set cookie on login. I'm able to login, but since no cookie is set, refreshing it logs me out and all routes give 401 error.
These are my django settings, removing secret_key here:
import os
from pathlib import Path
from datetime import timedelta
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_api_key',
'rest_framework_simplejwt.token_blacklist',
'corsheaders',
'django_filters',
'api'
]
AUTH_USER_MODEL = 'api.User'
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.AllowAllUsersModelBackend',
)
MIDDLEWARE = [
'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.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_HEADERS = [
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
"fxbNh4",
]
# CORS_ALLOWED_ORIGINS = [
# "http://localhost:8080"
# ]
# CORS_ALLOWED_ORIGINS = [
# 'http://34.67.29.99',
# ]
CORS_ALLOW_CREDENTIALS = True
ROOT_URLCONF = 'InstaBackend.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR)],
'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 = 'InstaBackend.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Asia/Karachi'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 12,
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated'
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'api.authenticate.CustomAuthentication',
),
'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S",
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': True,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
'AUTH_ACCESS_COOKIE': 'access_token', # Cookie name. Enables cookies if value is set.
'AUTH_REFRESH_COOKIE': 'refresh_token', # Cookie name. Enables cookies if value is set.
'AUTH_CSRF_ACCESS_COOKIE': 'csrf_access_token', # Cookie name. Enables cookies if value is set.
'AUTH_CSRF_REFRESH_COOKIE': 'csrf_refresh_token', # Cookie name. Enables cookies if value is set.
'AUTH_COOKIE_DOMAIN': None, # A string like "example.com", or None for standard domain cookie.
'AUTH_COOKIE_SECURE': False, # Whether the auth cookies should be secure (https:// only).
'AUTH_COOKIE_HTTP_ONLY': False, # Http only cookie flag.It's not fetch by javascript.
'AUTH_COOKIE_PATH': '/', # The path of the auth cookie.
'AUTH_COOKIE_SAMESITE': None, # Whether to set the flag restricting cookie leaks on cross-site requests.
# This can be 'Lax', 'Strict', or None to disable the flag.
}
# CSRF_COOKIE_DOMAIN = '34.67.29.99'
# # CSRF_COOKIE_SECURE = True
# # USE_X_FORWARDED_HOST = True
# # SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# CSRF_TRUSTED_ORIGINS = ['http://34.67.29.99/','http://34.67.29.99',]
# CSRF_COOKIE_SAMESITE = 'Lax'
# CSRF_COOKIE_HTTPONLY = False
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
This is how I set cookies :
def set_access_cookies(response, access_token):
response.set_cookie(
key=settings.SIMPLE_JWT['AUTH_ACCESS_COOKIE'],
value=access_token,
expires=settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
secure=settings.SIMPLE_JWT['AUTH_COOKIE_SECURE'],
httponly=settings.SIMPLE_JWT['AUTH_COOKIE_HTTP_ONLY'],
samesite=settings.SIMPLE_JWT['AUTH_COOKIE_SAMESITE']
)
def set_refresh_cookies(response, refresh_token):
response.set_cookie(
key=settings.SIMPLE_JWT['AUTH_REFRESH_COOKIE'],
value=refresh_token,
expires=settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
secure=settings.SIMPLE_JWT['AUTH_COOKIE_SECURE'],
httponly=settings.SIMPLE_JWT['AUTH_COOKIE_HTTP_ONLY'],
samesite=settings.SIMPLE_JWT['AUTH_COOKIE_SAMESITE']
)
This is my login_view:
from django.middleware import csrf
from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.auth import authenticate
from rest_framework import status
from api.util import (set_access_cookies, set_refresh_cookies, get_tokens_for_user, combine_role_permissions,
extract_patient_role)
from api.serializers import UserSerializer, RoleSerializer
from django.utils import timezone
from api.models import User
from rest_framework_api_key.permissions import HasAPIKey
from rest_framework.permissions import AllowAny
class LoginView(APIView):
authentication_classes = ()
permission_classes = (HasAPIKey,)
# permission_classes = (AllowAny,)
def post(self, request):
try:
data = request.data
response = Response()
username = data.get('username', None)
password = data.get('password', None)
try:
User.objects.get(email = username)
except User.DoesNotExist:
return Response({"msg": "User Credentails don't exist ! "}, status=status.HTTP_400_BAD_REQUEST)
user = authenticate(username=username, password=password)
if user.is_approved:
if user is not None:
role, roles = extract_patient_role(user.roles.all())
permissions = combine_role_permissions(roles)
data = get_tokens_for_user(user, is_patient=False)
set_access_cookies(response, data['access'])
set_refresh_cookies(response, data['refresh'])
csrf.get_token(request)
data = UserSerializer(user, context={'request': request}).data
data['roles'] = RoleSerializer(roles, many=True).data
data['permissions'] = permissions
response.status_code = status.HTTP_200_OK
response.data = {"msg": "Login successfully", "user": data}
user.last_login = timezone.now()
user.save()
return response
else:
return Response({"msg": "Invalid credentials"}, status=status.HTTP_404_NOT_FOUND)
else:
return Response({"msg": "Please verify account to log in"}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
print(e)
return Response({"msg": "Unable to Login"}, status=status.HTTP_400_BAD_REQUEST)
This is the axios instance for logging in:
import Vue from "vue";
import axios from "axios";
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== "") {
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const axiosIns2 = axios.create({
baseURL: process.env.VUE_APP_API,
withCredentials: true,
xsrfCookieName: "csrftoken",
xsrfHeaderName: "X-CSRFTOKEN",
headers: {
"X-CSRFTOKEN": getCookie("csrftoken"),
Authorization: "Api-Key " + process.env.VUE_APP_KEY,
},
});
Vue.prototype.$http = axiosIns2;
export default axiosIns2;
And this is the axios instance for other authenticated requests:
import Vue from "vue";
import axios from "axios";
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== "") {
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const axiosIns = axios.create({
baseURL: process.env.VUE_APP_API,
withCredentials: true,
xsrfCookieName: "csrftoken",
xsrfHeaderName: "X-CSRFTOKEN",
headers: {
"X-CSRFTOKEN": getCookie("csrftoken"),
},
});
const COOKIE_EXPIRED_MSG = "Token is invalid or expired";
axiosIns.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
let error_message = null;
if (error.response.data.messages) {
error_message = error.response.data.messages[0].message;
}
switch (error.response.status) {
case 401:
if (!error.config.retry && error_message === COOKIE_EXPIRED_MSG) {
error.config.retry = true;
// axiosIns.defaults.xsrfCookieName = "csrf_refresh_token";
await axiosIns.post("/refresh");
// axiosIns.defaults.xsrfCookieName = "csrf_access_token";
return axiosIns(error.config);
} else {
throw new Error("Error");
}
break;
default:
break;
}
return Promise.reject(error);
}
);
Vue.prototype.$http = axiosIns;
export default axiosIns;
VPS OS Details:
Ubuntu 18.04.6
I currently don't have access to VPS, i believe this issue is due to SIMPLE_JWT settings in settings.py.
The project runs perfectly fine with these settings locally, but it doesn't send cookies on login when hosted. It does give 200 on login though.
Hosted Via:
Nginx , Gunicorn
The backend and frontend are hosted on 2 different hosts,
backend at : http://34.67.29.99:8080/
Frontend at : http://instasearch.convpho.com/login
I've created a DRF app which the backend is using jwt authentication with httpolnly cookies for authentication and it also uses a enforce_csrf for perventing csrf attacks.
from rest_framework_simplejwt.authentication import JWTAuthentication
from django.conf import settings
from rest_framework.authentication import CSRFCheck
from rest_framework import exceptions
def enforce_csrf(request):
check = CSRFCheck()
check.process_request(request)
reason = check.process_view(request, None, (), {})
print(reason)
if reason:
raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)
class CookieBasedJWTAuthentication(JWTAuthentication):
def authenticate(self, request):
header = self.get_header(request)
if header is None:
raw_token = request.COOKIES.get(settings.SIMPLE_JWT['AUTH_COOKIE_ACCESS']) or None
else:
raw_token = self.get_raw_token(header)
if raw_token is None:
return None
validated_token = self.get_validated_token(raw_token)
enforce_csrf(request)
return self.get_user(validated_token), validated_token
to pervent csrf attacks django set a cookie and also a 'csrftokenmiddleware' and compares them.
here is the sample code for setting csrf cookie and token:
class SetCSRFToken(APIView):
permission_classes = [AllowAny]
def get(self, request):
response = Response()
csrf.get_token(request)
response.status_code = status.HTTP_200_OK
csrf_secret = csrf.get_token(request)
response.set_cookie(
key = 'csrftoken',
value = request.META["CSRF_COOKIE"],
expires = settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
path= settings.SIMPLE_JWT['AUTH_COOKIE_PATH'],
secure = settings.SIMPLE_JWT['AUTH_COOKIE_SECURE'],
httponly = False,
samesite = 'Lax'
)
response.data = {"status": "CSRF cookie set", 'csrfmiddlewaretoken':request.META["CSRF_COOKIE"]}
return response
i also set :
CORS_ALLOW_ALL_ORIGINS= True
CORS_ALLOW_CREDENTIALS = True
the code works perfect when both frontend and backend are on the localhost, but when i run the backend on a remote server, the cookies are not set but the browser receives the Response to request.
when frontend is on the same host
when backend on remote server and frontend on localhsot
this is the console result for both cases
this is the settings, I've tried using CSRF options, but still not setting the cookie, what is weired is when i call out to my apiView from browser(rather than javascript) the cookie is being set.
settings.py:
"""
Django settings for core project.
Generated by 'django-admin startproject' using Django 4.0.2.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
from pathlib import Path
import os
from .info import *
from datetime import timedelta
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
EMAIL_USE_TLS = EMAIL_USE_TLS
EMAIL_HOST = EMAIL_HOST
EMAIL_HOST_USER = EMAIL_HOST_USER
EMAIL_HOST_PASSWORD = EMAIL_HOST_PASSWORD
EMAIL_PORT = EMAIL_PORT
FRONTEND_URL = FRONTEND_URL
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-yzvg4k4r0%*3001&oo&up*#-yvcq(k#tpsdi^g=*ql#zvtogav'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['myip', 'localhost', '127.0.0.1']
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
CSRF_COOKIE_SAMESITE = None
CSRF_TRUESTED_ORIGINS =['myip', 'localhost', '127.0.0.1']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api',
'rest_framework',
'corsheaders',
'users',
'rest_framework_simplejwt.token_blacklist',
]
MIDDLEWARE = [
'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.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'core.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'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 = 'core.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CORS_ALLOW_ALL_ORIGINS= True
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = [
"http://myip:3000",
"http://127.0.0.1:3000",
"http://localhost:3000",
]
'''
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
)
}
'''
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
] ,
'DEFAULT_AUTHENTICATION_CLASSES': (
#'rest_framework_simplejwt.authentication.JWTAuthentication',
'users.authentication.CookieBasedJWTAuthentication',
)
}
AUTH_USER_MODEL = 'users.DearUser'
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'UPDATE_LAST_LOGIN': False,
'AUTH_HEADER_TYPES': ('Bearer','JWT'),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'USER_ID_FIELD': 'email',
'USER_ID_CLAIM': 'email',
# cookie based jwt settings
'AUTH_COOKIE_ACCESS':'ACCESS',
'AUTH_COOKIE_REFRESH':'REFRESH',
'AUTH_COOKIE_SECURE': False,
'AUTH_COOKIE_HTTP_ONLY' : True,
'AUTH_COOKIE_PATH': '/',
'AUTH_COOKIE_SAMESITE': 'None', #Strict
}
Found it!
First make sure you got everything right in this answer:
https://stackoverflow.com/a/46412839/18327111
because django corsheaders middleware is checking the below if, make sure to have the following settings:
if conf.CORS_ALLOW_ALL_ORIGINS and not conf.CORS_ALLOW_CREDENTIALS:
response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
else:
response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin
new settings:
from corsheaders.defaults import default_headers
CORS_ALLOW_ALL_ORIGINS= False
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = [
"http://yourip_or_domain:3000",
"http://127.0.0.1:3000",
"http://localhost:3000",
]
#making sure CORS_ALLOW_HEADERS is not "*"
CORS_ALLOW_HEADERS = list(default_headers) + ['Set-Cookie']
in case you are not using django session authentication(as i do) and want to bypass it add the following settings
CSRF_USE_SESSIONS = False
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
CSRF_COOKIE_SAMESITE = None
SESSION_COOKIE_SAMESITE = None
and the main reason it failed: this is copied from chrome developer tools and mozila also have this warning:
Because a cookie’s SameSite attribute was not set or is invalid, it
defaults to SameSite=Lax, which prevents the cookie from being sent in
a cross-site request. This behavior protects user data from
accidentally leaking to third parties and cross-site request forgery.
Resolve this issue by updating the attributes of the cookie:
Specify SameSite=None and Secure if the cookie should be sent in
cross-site requests. This enables third-party use.
Specify SameSite=Strict or SameSite=Lax if the cookie should not be
sent in cross-site requests.
so if you want to access cross origin, the only way is using an https and SameSite=None, otherwise you have to deploy your api and backend on the same domain
Note that http and https are considered as different domain and are cross-origin
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#samesitenone_requires_secure
Cookies from the same domain are no longer considered to be from the
same site if sent using a different scheme (http: or https:).
I created a django project tha uses a customUser for authentication, following this blog post.
I now would like to attach another app to the project that is a REST API that leverages that authentication.
In this new app I create a model for the data that the uses will consume and I add as foreign key in the model the custom user model previously created.
#project/api/models.py
from django.contrib.postgres.fields import ArrayField
from django.db import models
from accounts.models import CustomUser
class Signal(models.Model):
name = models.CharField(max_length=30, blank=True, null=True)
data = ArrayField(models.FloatField(), unique=True)
threshold = models.FloatField(blank=True, null=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
def __str__(self) -> str:
return f"{self.name}"
while the project/api/views.py file is
from django.template.defaultfilters import pluralize
from rest_framework.decorators import api_view
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from . import serializers
from .algorithm.compute_crossing import count_crossing_pos as count_cross
from .models import Signal
class SignalViewset(ListCreateAPIView):
permission_classes = [IsAuthenticated]
queryset = Signal.objects.all()
serializer_class = serializers.SignalSerializer
def get_queryset(self):
return super().get_queryset().filter(user=self.request.user)
class SignalDetail(RetrieveUpdateDestroyAPIView):
permission_classes = [IsAuthenticated]
queryset = Signal.objects.all()
serializer_class = serializers.SignalSerializer
def get_queryset(self):
return super().get_queryset().filter(user=self.request.user)
#api_view(http_method_names=["POST"])
def send_data(request: Request) -> Response():
"""send signal data with threshold in request payload
and compute crossing times around given threshold
:param request: [request payload]
:type request: [type]
"""
if request.user.is_authenticated:
count = count_cross(request.data.get("data"), request.data.get("threshold"))
return Response(
{
"status": "success",
"info": "signal crosses the given threshold {} tim{}".format(
count, pluralize(count, "e,es")
),
"count": str(count),
},
status=200,
)
else:
count = count_cross(request.data.get("data"), request.data.get("threshold"))
return Response(
{
"status": "success",
"info": "you are an AnonymousUser but I will give you an answer nonetheless \n \n signal crosses the given threshold {} tim{}".format(
count, pluralize(count, "e,es")
),
"count": str(count),
},
status=200,
)
Everytime I access the send_data endpoint I go into the else statement as request.user.is_authenticated is False.
how do I connect the rest api with the authentication (accounts) app?
my settings.py file is:
from pathlib import Path
from decouple import config
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
SYSTEM_ENV = config("SYSTEM_ENV")
ALLOWED_HOSTS = [
"*",
]
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"api",
"rest_framework",
"rest_framework.authtoken",
"dj_rest_auth",
"django.contrib.sites",
"allauth",
"allauth.account",
"dj_rest_auth.registration",
"accounts",
]
AUTH_USER_MODEL = "accounts.CustomUser" # new
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "signal_crossing.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [str(BASE_DIR.joinpath("templates"))],
"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 = "signal_crossing.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
if SYSTEM_ENV == "PRODUCTION" or SYSTEM_ENV == "STAGING":
print("docker")
DEBUG = False
SECRET_KEY = config("SECRET_KEY")
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": "postgres",
"USER": "postgres",
"PASSWORD": "postgres",
"HOST": "db",
"PORT": 5432,
}
}
elif SYSTEM_ENV == "GITHUB_WORKFLOW":
DEBUG = True
SECRET_KEY = "TESTING_KEY"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": "github_actions",
"USER": "postgres",
"PASSWORD": "postgres",
"HOST": "127.0.0.1",
"PORT": "5432",
}
}
elif SYSTEM_ENV == "DEVELOPMENT":
DEBUG = True
SECRET_KEY = "DEVELOP_KEY"
# DATABASES = {
# "default": {
# "ENGINE": "django.db.backends.postgresql_psycopg2",
# "NAME": config("DB_NAME"),
# "USER": config("POSTGRES_LOCAL_USER"),
# "PASSWORD": config("POSTGRES_LOCAL_PASSWORD"),
# "HOST": "localhost",
# "PORT": "5432",
# }
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.2/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/3.2/howto/static-files/
STATIC_URL = "/static/"
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
SITE_ID = 1
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
# 'rest_framework.authentication.TokenAuthentication',
"dj_rest_auth.jwt_auth.JWTCookieAuthentication",
],
}
REST_USE_JWT = True
JWT_AUTH_COOKIE = "my-app-auth"
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
# allauth specific authentication methods, such as login by e-mail
"allauth.account.auth_backends.AuthenticationBackend",
# Needed to login by username in Django admin, regardless of allauth
"django.contrib.auth.backends.ModelBackend",
]
# ACCOUNT_AUTHENTICATION_METHOD = "username"
# LOGIN_URL = "/login/"
# LOGIN_REDIRECT_URL = "/"
# LOGOUT_REDIRECT_URL = "/"
DJANGO_SETTINGS_MODULE = "signal_crossing.settings"
LOGIN_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "home"
I am creating a Django REST project.
I created a custom user model in which I used rest-knox for token authentication. For the login and register user endpoints I used custom views where I authenticate the user's by their knox token. For password_change endpoint I used django auth's views.
Here is the problem I am facing:
When I try password_change endpoint in browsable API; it redirects me again to login page and does nothing. However Knox token for the user is created and returned in JSON.
When I try the endpoint in Postman it gives CSRF token error since I cannot get the CSRF token.
I tried to create my own front-end page for changing password and sometimes the 'csrftoken' was returned in cookie table of chrome but not all the time. Since I get the user token with JSON response I set it to cookies on React.js and reach anytime I want. Yet, get method of react-cookie doesn't work for csrftoken and it is being set as undefined. Finally I tried to use axios library for my post request and set default header for 'csrftoken' but couldn't see any results. The front-end 'localhost:3000/change_password/' page just redirects to itself by adding passed parameters to the end of url.
views.py
from django.shortcuts import render
from rest_framework import viewsets, generics
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from knox.auth import TokenAuthentication
from knox.models import AuthToken
from rest_framework.permissions import IsAuthenticated, AllowAny
from .models import UserProfile
from .serializers import UserSerializer, LoginSerializer, KnoxSerializer, RegisterSerializer
from django.contrib.auth import views, decorators
from django.contrib.auth.decorators import user_passes_test
class UserListAPI(generics.ListAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,)
queryset = UserProfile.objects.all()
serializer_class = UserSerializer
class UserDetailAPI(generics.RetrieveAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,)
queryset = UserProfile.objects.all()
serializer_class = UserSerializer
lookup_field = 'username'
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
token = AuthToken.objects.create(user)[1]
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token
})
class RegisterAPI(generics.GenericAPIView):
serializer_class = RegisterSerializer
queryset = UserProfile.objects.all()
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1]
})
settings.py
import os
from decouple import config
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
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/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'knox',
'rest_framework.authtoken',
'accounts',
'rest_auth',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'Book_Lib_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 = 'Book_Lib_Project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.0/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/3.0/howto/static-files/
STATIC_URL = '/static/'
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication',),
'DATETIME_FORMAT': ("%m/%d/%Y %H:%M:%S",),
'DEFAULT_PERMISSION_CLASSES':(
'rest_framework.permissions.AllowAny',),
}
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
# #"accounts.backends.EmailAuthenticationBackend",
]
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
]
from datetime import timedelta
from rest_framework.settings import api_settings
REST_KNOX = {
'SECURE_HASH_ALGORITHM': 'cryptography.hazmat.primitives.hashes.SHA512',
'AUTH_TOKEN_CHARACTER_LENGTH': 64,
'TOKEN_TTL': timedelta(hours=10),
'USER_SERIALIZER': 'knox.serializers.UserSerializer',
'TOKEN_LIMIT_PER_USER': None,
'AUTO_REFRESH': False,
'EXPIRY_DATETIME_FORMAT': api_settings.DATETIME_FORMAT,
}
urls.py
from django.urls import path, include
from .views import UserListAPI, UserDetailAPI, LoginAPI, RegisterAPI, PasswordChangeAPI
from knox import views as knox_views
urlpatterns = [
path('logout/', knox_views.LogoutView.as_view(), name = "knox_logout"),
path('users/', UserListAPI.as_view()),
path('users/<str:username>/', UserDetailAPI.as_view()),
path('login/', LoginAPI.as_view()),
path('register/', RegisterAPI.as_view()),
path('', include('django.contrib.auth.urls')),
]
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import axios from 'axios';
axios.defaults.withCredentials = true
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
axios.interceptors.request.use(request => {
console.log(request.headers);
// Edit request config
return request;
}, error => {
console.log(error);
return Promise.reject(error);
});
axios.interceptors.response.use(response => {
console.log(response);
// Edit response config
return response;
}, error => {
console.log(error);
return Promise.reject(error);
});
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
ChangePassword.js
import React, {Component} from 'react';
//import ReactDOM from 'react-dom';
import { withCookies } from 'react-cookie';
import './ChangePassword.css';
import Axios from 'axios'
class ChangePassword extends Component {
state = {
credentials: {
old_password: '',
new_password1: '',
new_password2: '',
},
token: this.props.cookies.get('usertoken'),
}
inputChanged = event => {
let cred = this.state.credentials;
cred[event.target.name] = event.target.value;
this.setState({credentials: cred});
}
pressedEnter = event => {
if (event.key === 'Enter') {
this.changePassword();
}
}
changePassword = event => {
Axios.post('http://127.0.0.1:8000/accounts/password_change/', this.state.credentials, {headers: {
'Authorization': `Token ${this.state.token}`
}})
.then(res => {
console.log(res);
window.location.href = "/";
})
.catch( error => console.log(error))
}
render() {
return (
<div className="Login">
<form onSubmit={this.changePassword}>
<label>Old Password</label>
<input type="text" name="old_password" value={this.state.credentials.email} onChange={this.inputChanged} onKeyPress={this.pressedEnter}/><br/>
<label>New Password</label>
<input type="password" name="new_password1" value={this.state.credentials.password} onChange={this.inputChanged} onKeyPress={this.pressedEnter}/><br/>
<label>Confirm New Password</label>
<input type="password" name="new_password2" value={this.state.credentials.password} onChange={this.inputChanged} onKeyPress={this.pressedEnter}/><br/>
<input type="submit" value="Change" data-test="submit" />
</form>
</div>
)
}
}
export default withCookies(ChangePassword);
I solved the problem, so here is the solution if anyone encounters with the same problem and looks here.
At first, I needed to use the domain as http://127.0.0.1:3000 instead of http://localhost:3000 because of the restrictions given in https://curl.haxx.se/rfc/cookie_spec.html (check the section named as DOMAIN). Also, making some change on setting.py file was required.
Added below information to my settings.py file
CORS_ORIGIN_WHITELIST = (
'http://127.0.0.1:3000',
)
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_EXPOSE_HEADERS = (
'Access-Control-Allow-Origin: http://127.0.0.1:3000',
)
Changed my axios configuration in index.js to
axios.defaults.withCredentials = true
axios.defaults.baseURL = 'http://127.0.0.1:8000/';
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'x-csrftoken'
A proper way to make a post for login page is like:
Axios.post('http://127.0.0.1:8000/accounts/login/', this.state.credentials).then(res => {
console.log(res);
this.props.cookies.set('usertoken', res.data.token);
// Used token based authentication. A token is returned from backend in JSON format.
}).catch( error => console.log(error))
I've read through multiple examples and tried all of the code - for some strange reason none of it is working with my current project.
If I use postman I can add users without problem.
When I try to replicate my postman requests in React, it goes through just the same but no user details are saved to my Django database.
I've tried Django rest, researching and reading threads for two days.
Django Settings
"""
Django settings for demo_project project.
Generated by 'django-admin startproject' using Django 2.1.7.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
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/2.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '58u29^u#wy%90)%ni=u5muf1h(g#*el*o$q%nvnxji3*yg!1hl'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['localhost']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'users',
'pages',
]
# custom user model
AUTH_USER_MODEL = 'users.CustomUser'
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'demo_project.urls'
# redirect urls for auth
LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'home'
# custom authentication backends
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
)
SITE_ID = 1
ACCOUNT_EMAIL_REQUIRED = True # we want to store their email in django
ACCOUNT_USERNAME_REQUIRED = False # we're not worried about user names
# CORS config
# CORS_ORIGIN_WHITELIST = (
# 'localhost:5000',
# '127.0.0.1:5000'
# 'localhost:3000',
# '127.0.0.1:3000'
# )
# CORS_ORIGIN_ALLOW_ALL = True
# CORS_ALLOW_CREDENTIALS = True
# CORS_ALLOW_HEADERS = (
# 'xsrfheadername',
# 'xsrfcookiename',
# 'content-type',
# "X-CSRFTOKEN"
# )
#CSRF_COOKIE_NAME = "XSRF-TOKEN"
CSRF_COOKIE_NAME = "csrftoken"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'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 = 'demo_project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.1/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/2.1/howto/static-files/
STATIC_URL = '/static/'
React Frontend Form
import React, { Component } from "react";
import axios from "axios";
import "./App.css";
import cookie from "react-cookies";
axios.defaults.xsrfHeaderName = "X-CSRFToken";
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.withCredentials = true;
class App extends Component {
constructor(props) {
super(props);
this.state = {
email: "hihi#gmail.com",
password1: "test123T",
password2: "test123T"
};
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleSubmit = e => {
e.preventDefault();
var myUrl = "/accounts/signup/";
var myData = "email=hihi%40gmail.com&password1=test123T&password2=test123T";
axios
.post(myUrl, myData, {
headers: {
"X-CSRFToken": cookie.load("csrftoken"),
"Content-Type": "application/x-www-form-urlencoded",
"cache-control": "no-cache"
}
// other configuration there
})
.then(function(response) {
alert("yeah!");
console.log(response);
})
.catch(function(error) {
alert("oops");
console.log(error);
});
};
render() {
return (
<div className="App">
<header className="App-header">
<h3>This is going to be a register form hopefully</h3>
<form onSubmit={this.handleSubmit}>
{/* {% csrf_token %} */}
{/* {{ form.as_p }} */}
<input
placeholder="email"
onChange={this.handleChange}
type="email"
name="email"
/>
<input
placeholder="pw1"
onChange={this.handleChange}
type="password"
name="password1"
/>
<input
placeholder="pw2"
onChange={this.handleChange}
type="password"
name="password2"
/>
<button type="submit">Sign up</button>
</form>
</header>
</div>
);
}
}
export default App;
Proxy in Package json
"proxy": "http://localhost:8000"
My request headers and form data come through no worries
Request headers
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
cache-control: no-cache
Connection: keep-alive
Content-Length: 60
Content-Type: application/x-www-form-urlencoded
Cookie: csrftoken=pw59YcUID3RjZOOmcJlIUGNv1sBZQlRvX1hcNOkNVzMOEl0ETRUeZkTCU0lnVO1s; sessionid=17j037bo4oju0v6jprpkuv6x26m0eajt
Host: localhost:3000
Origin: http://localhost:3000
Referer: http://localhost:3000/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
X-CSRFToken: pw59YcUID3RjZOOmcJlIUGNv1sBZQlRvX1hcNOkNVzMOEl0ETRUeZkTCU0lnVO1s
Form data
email: hihi#gmail.com
password1: test123T
password2: test123T
My backend used to error saying there's no CSRF token present, but that error no longer occurs.
I also hit an obstacle where my frontend would get 'forbidden' but this no longer happens.
When I check my Django users DB, no user has been added :(
If I fire up postman, I can add users without problem.
Postman info
POST /accounts/signup/ HTTP/1.1
Host: localhost:8000
X-CSRFToken: F8MuraVaDK3tUDwD39lVPthQgdkL4HPm
Content-Type: application/x-www-form-urlencoded
cache-control: no-cache
Postman-Token: d34b79e6-fd5b-44a2-9ed0-74d28fee882e
email=test%40gmail.compassword1=test123Tpassword2=test123T
I'm starting to get quite worn out on getting this going. So I'm hoping some amazing human out there will help to unblock me.
If you wish to clone / pull the repo let me know.