Troubleshooting 401 Unauthorized and 400 Bad Request Errors in Django and Next.js
I am making a news app using django (JWT) and nextJS(Redux, RTK query). When I try to retrieve user using useRetrieveUserQuery()
, When I log into my account, I get GET http://localhost:8000/api/users/me/ 401 (Unauthorized)
otherwise POST http://localhost:8000/api/jwt/refresh/ 400 (Bad Request)
when coming from another user page.
Here are my views -
from django.conf import settings
import requests
from datetime import datetime, timedelta
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView
)
import logging
from collections import deque
logger = logging.getLogger(__name__)
class CustomTokenObtainPairView(TokenObtainPairView):
def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
if response.status_code == 200:
access_token = response.data.get('access')
refresh_token = response.data.get('refresh')
response.set_cookie(
'access',
access_token,
max_age=settings.AUTH_COOKIE_ACCESS_MAX_AGE,
path=settings.AUTH_COOKIE_PATH,
secure=settings.AUTH_COOKIE_SECURE,
httponly=settings.AUTH_COOKIE_HTTP_ONLY,
samesite=settings.AUTH_COOKIE_SAMESITE
)
response.set_cookie(
'refresh',
refresh_token,
max_age=settings.AUTH_COOKIE_REFRESH_MAX_AGE,
path=settings.AUTH_COOKIE_PATH,
secure=settings.AUTH_COOKIE_SECURE,
httponly=settings.AUTH_COOKIE_HTTP_ONLY,
samesite=settings.AUTH_COOKIE_SAMESITE
)
return response
class CustomTokenRefreshView(TokenRefreshView):
def post(self, request, *args, **kwargs):
refresh_token = request.COOKIES.get('refresh')
if refresh_token:
request.data['refresh'] = refresh_token
response = super().post(request, *args, **kwargs)
if response.status_code == 200:
access_token = response.data.get('access')
response.set_cookie(
'access',
access_token,
max_age=settings.AUTH_COOKIE_ACCESS_MAX_AGE,
path=settings.AUTH_COOKIE_PATH,
secure=settings.AUTH_COOKIE_SECURE,
httponly=settings.AUTH_COOKIE_HTTP_ONLY,
samesite=settings.AUTH_COOKIE_SAMESITE
)
return response
class CustomTokenVerifyView(TokenVerifyView):
def post(self, request, *args, **kwargs):
access_token = request.COOKIES.get('access')
if access_token:
request.data['token'] = access_token
return super().post(request, *args, **kwargs)
class LogoutView(APIView):
def post(self, request, *args, **kwargs):
response = Response(status=status.HTTP_204_NO_CONTENT)
response.delete_cookie('access')
response.delete_cookie('refresh')
return response
here is models.py
-
from django.db import models
from django.contrib.auth.models import (
BaseUserManager,
AbstractBaseUser,
PermissionsMixin
)
class UserAccountManager(BaseUserManager):
def create_user(self, email, password=None, **kwargs):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError("Users must have an email address")
'test@EXAMPLE.COM -> test@example.com'
email = self.normalize_email(email)
email = email.lower()
user = self.model(
email=email,
**kwargs
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **kwargs):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
user = self.create_user(
email,
password=password,
**kwargs
)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class UserAccount(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(
unique=True,
max_length=255
)
# preferences = models.JSONField(default=list) # Store preferences as JSON
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
objects = UserAccountManager()
USERNAME_FIELD = 'email'
# REQUIRED_FIELDS = ['password']
def __str__(self):
return self.email
On the frontend, I create authApiSlice.ts
-
import { apiSlice } from '../services/apiSlice';
interface User {
email: string;
}
const authApiSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
retrieveUser: builder.query<User, void>({
query: () => '/users/me',
}),
...
}),
});
export const {
useRetrieveUserQuery,
...
} = authApiSlice;
apiSlice.ts -
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query';
import { setAuth, logout } from '../features/authSlice';
import { Mutex } from 'async-mutex';
const mutex = new Mutex();
const baseQuery = fetchBaseQuery({
baseUrl: `${process.env.NEXT_PUBLIC_HOST}/api`,
credentials: 'include',
});
const baseQueryWithReauth: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
await mutex.waitForUnlock();
let result = await baseQuery(args, api, extraOptions);
if (result.error && result.error.status === 401) {
if (!mutex.isLocked()) {
const release = await mutex.acquire();
try {
const refreshResult = await baseQuery(
{
url: '/jwt/refresh/',
method: 'POST',
},
api,
extraOptions
);
if (refreshResult.data) {
api.dispatch(setAuth());
result = await baseQuery(args, api, extraOptions);
} else {
api.dispatch(logout());
}
} finally {
release();
}
} else {
await mutex.waitForUnlock();
result = await baseQuery(args, api, extraOptions);
}
}
return result;
};
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: baseQueryWithReauth,
endpoints: builder => ({}),
});
on my home page, I call useRetrieveUserQuery()
-
'use client';
import FeedTray from '@/components/news/feedTray';
import Navbar from '@/components/navbar';
import { useRetrieveUserQuery, useNewsMutation } from '@/redux/features/authApiSlice';
interface NavLink {
name: string;
path: string;
highlight?: boolean;
onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;
}
interface NewsItem {
title: string;
url: string;
des_facet: string[];
}
interface UserData {
email: string;
preferences: string[];
isFirstTime: boolean;
}
const Home: React.FC = () => {
const {data: user, isLoading, isFetching} = useRetrieveUserQuery();
const config = [
{
label: 'Email',
value: user?.email,
},
];
const username = config[0].value?.split('@')[0]
console.log('username - ' + config[0].label)
const navLinks: NavLink[] = [
{ name: "News", path: "/home_page" },
{ name: username || "Profile", path: "/profile", highlight: true },
];
if (isLoading || isFetching) {
return <div className="text-white text-center">Loading...</div>;
}
return (
<div className="w-full bg-black text-white">
<Navbar links={navLinks} />
<FeedTray />
</div>
);
};
export default Home;
I start by logging into my account, then I get redirected to home page where I get the error. I tried matching User props in authApiSlice.ts
with the models in the backend but that fix the issue. I am not sure where is the issue. Any help would be appreciated!