I’ve been learning Django and am trying to move from the standard templates to a separate NextJS frontend supported by Django Rest Framework.
I implemented Django-allauth (headless) on the backend and NextAuth on the frontend, but I cannot get the Login to accept my login POST (403 error with CSRF Cookie not set) despite trying multiple tips online to get a valid CSRF token.
My NextJS frontend is at localhost:3000 and my Django backend at localhost:8000. The login endpoint works from unit tests in my Django app.
Process
Submit frontend form at http://localhost:3000/api/auth/signin
Django output:
Forbidden (CSRF cookie not set.): /_allauth/browser/v1/auth/login
[timestamp] "POST /_allauth/browser/v1/auth/login HTTP/1.1" 403
Django
settings.py
INSTALLED_APPS = [
...
"rest_framework",
"rest_framework_simplejwt",
"corsheaders",
"allauth",
"allauth.account",
"allauth.headless",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"corsheaders.middleware.CorsMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"allauth.account.middleware.AccountMiddleware",
]
ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"]
SESSION_COOKIE_DOMAIN = "localhost"
CSRF_COOKIE_DOMAIN = "localhost"
CSRF_COOKIE_SECURE = False
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_NAME = 'csrftoken'
CSRF_TRUSTED_ORIGINS = ["http://localhost:3000"]
CORS_ALLOWED_ORIGINS = ["http://localhost:3000"]
# AUTHENTICATION
LOGIN_REDIRECT_URL = "/"
AUTH_USER_MODEL = "authtools.User"
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"},
]
AUTHENTICATION_BACKENDS = [
# Needed to login by username in Django admin, regardless of `allauth`
"django.contrib.auth.backends.ModelBackend",
# `allauth` specific authentication methods, such as login by email
"allauth.account.auth_backends.AuthenticationBackend",
]
HEADLESS_ONLY = True
ACCOUNT_AUTHENTICATED_LOGIN_REDIRECTS = False
FRONTEND_URL = env("FRONTEND_URL", default="http://localhost:3000")
HEADLESS_FRONTEND_URLS = {
"account_confirm_email": FRONTEND_URL + "/account/verify-email/{key}",
"account_reset_password_from_key": FRONTEND_URL
+ "/account/password/reset/key/{key}",
"account_signup": FRONTEND_URL + "/account/signup",
}
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = "email"
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_CHANGE_EMAIL = True
# REST FRAMEWORK SETTINGS
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAdminUser",),
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
),
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 100,
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"AUTH_HEADER_TYPES": ("Bearer",),
}
urls.py
urlpatterns = [
path('csrf-token/', get_csrf_token, name='csrf-token'),
path('accounts/', include('allauth.urls')),
path("_allauth/", include("allauth.headless.urls")),
...
views.py
from django.http import JsonResponse
from django.middleware.csrf import get_token
def get_csrf_token(request):
token = get_token(request)
response = JsonResponse({"csrfToken": token})
response.set_cookie("csrftoken", token)
return response
NextJS
./utils/csrfCookie.ts
export async function getCsrfToken() {
const server_url = process.env.SERVER_URL;
const response = await fetch(server_url + "/api/csrf-token/", {
credentials: "include", // Important to include credentials for CSRF
});
if (!response.ok) {
throw new Error("Failed to fetch CSRF token");
}
const data = await response.json();
console.log("CSRF: ", data);
return data.csrfToken;
}
auth.ts
import { getCsrfToken } from "./utils/csrfCookie";
import Credentials from "next-auth/providers/credentials";
import NextAuth from "next-auth";
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Credentials({
credentials: {
email: { label: "Email", type: "text" },
password: { label: "Password", type: "password" },
},
authorize: async (credentials) => {
let user = null;
const server_url = process.env.SERVER_URL;
console.log("Credentials: ", credentials);
try {
const csrfToken = await getCsrfToken();
console.log("JSON CSRF: ", csrfToken);
const response = await fetch(
server_url + "/_allauth/browser/v1/auth/login",
{
method: "POST",
body: JSON.stringify({
email: credentials.email,
password: credentials.password
}),
credentials: "include",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken,
},
},
);
if (!response.ok) {
console.error("Failed to login:", response.statusText);
throw new Error("Failed to login");
}
user = await response.json();
console.log("User retrieved: ", user);
if (!user) {
throw new Error("User not found.");
}
return { ...user, token: user.token };
} catch (error) {
console.error("Authorization error:", error);
throw error;
}
},
}),
],
});
Michael Gribben is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.