Reputation: 71
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:
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,
Upvotes: 2
Views: 1261
Reputation: 71
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/");
},
}
Upvotes: 1