Why is POST request to /api/posts/ returning 401 Unauthorized in Django DRF despite valid token in Axios headers?

Problem:
I'm facing an issue with a 401 Unauthorized response when making a POST request to /api/posts/ in my Django Rest Framework (DRF) backend from a React frontend. The Axios instance in React uses an interceptor to add a Bearer token (stored in localStorage) to the Authorization header, but the request still fails.

The DRF view uses generics.ListCreateAPIView and requires authentication with permissions.IsAuthenticated. The POST request fails, even though the token is present in the headers. I've checked both the frontend and backend configurations, but the issue persists. The urlpattern is posts/. But from frontend it all the urlpatterns for the api(Django app) can only be accesed through /api/urlpattern/.


Django DRF Backend Code

When I use the url-pattern from the projects urls.py, I am able to create a post -

python
from django.contrib import admin
from django.urls import path, include
from api.views import CreateUserView, ProfileView, PostListCreateView
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from django.conf import settings
from django.conf.urls.static import static


urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/user/register/', CreateUserView.as_view(), name='register'),
    path('api/token/', TokenObtainPairView.as_view(), name='get_token'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='refresh'),
    path('api/posts/', PostListCreateView.as_view(), name='post-list'),
    path('api_auth/', include('rest_framework.urls')),
    path('api/', include('api.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

But when I use the api's urls.py, it gives me 405 METHOD "POST" not allowed.

python
from django.urls import path
from . import views


urlpatterns = [
    path('', views.WelcomeView.as_view(), name='welcome'),
    path('home/', views.HomeView.as_view(), name='home'),
    path('<str:username>/update-profile/', views.UpdateProfileView.as_view(), name='update-profile'),
    path('<str:username>/', views.ProfileView.as_view(), name="profile"),
    path('posts/', views.PostListCreateView.as_view(), name='post-list'),
    path('posts/<int:pk>/', views.PostDetailView.as_view(), name='post-detail'), 
    path('posts/<int:post_pk>/likes/', views.LikesListCreateView.as_view(), name='like-list'),
    path('likes/<int:pk>/', views.LikesDetailView.as_view(), name='like-detail'),
    path('posts/<int:post_pk>/comments/', views.CommentListCreateView.as_view(), name='comment-list'),
    path('comments/<int:pk>/', views.CommentDetailView.as_view(), name='comment-detail'),
]

models.py

class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
    body = models.TextField(max_length=240)
    media = models.ImageField(null=True, blank=True)
    created_at = models.DateTimeField(default=timezone.now)

    def number_of_likes(self):
        return self.likes.count()

    def number_of_comments(self):
        return self.comments.count()

    def get_created_at_in_user_timezone(self, author):
        user_timezone = pytz.timezone(author.profile.timezone)
        return self.created_at.astimezone(user_timezone)

class PostSerializer(serializers.ModelSerializer):
    author = serializers.StringRelatedField(source='author.username', read_only=True)
    number_of_likes = serializers.SerializerMethodField()
    number_of_comments = serializers.SerializerMethodField()

    def get_number_of_likes(self, obj):
        return obj.likes.count()

    def get_number_of_comments(self, obj):
        return obj.comments.count()

    class Meta:
        model = Post
        fields = ['id', 'author', 'body', 'media', 'created_at', 'number_of_likes', 'number_of_comments']
        read_only_fields = ['author', 'created_at', 'number_of_likes', 'number_of_comments']

class PostListCreateView(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]

    def get_queryset(self):
        user = self.request.user
        return Post.objects.filter(author=user)
    
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

React Frontend Code

api.js

import axios from 'axios'
import { ACCESS_TOKEN } from './constants'

const api = axios.create({
    baseURL: import.meta.env.VITE_API_URL
})

api.interceptors.request.use(
    (config) => {
        if (config.url && !config.url.includes('/api/user/register/')) {
            const token = localStorage.getItem(ACCESS_TOKEN);
            if (token) {
                config.headers.Authorization = `Bearer ${token}`
            }
        }
        return config
    },
    (error) => {
        return Promise.reject(error)
    }
)

export default api;

HomeView.jsx

const username = localStorage.getItem('username');
const { profile, loading } = useProfile(username);
const [postData, setPostData] = useState({
    body: "",
    media: null,
});

// Handle file change (e.g., media upload)
const handleFileChange = (e) => {
    const { name, files } = e.target;
    setPostData((prevData) => ({
        ...prevData,
        [name]: files[0], // Update the file in postData
    }));
};

// Handle text input changes
const handleChange = (e) => {
    const { name, value } = e.target;
    setPostData((prevData) => ({
        ...prevData,
        [name]: value, // Update the body in postData
    }));
};

// Handle form submission
const makePost = async (e) => {
    e.preventDefault();

    const formData = new FormData();

    Object.keys(postData).forEach((key) => {
        if (postData[key] !== "" && postData[key] !== null) {
            formData.append(key, postData[key]);
        }
    });

    try {
        const response = await api.post("/api/posts/", formData, {
            headers: {
                "Content-Type": "multipart/form-data",
            },
        });

        if (response.status === 201) {
            setPostData({ body: "", media: null });
            alert("Successfully created post");
        } else {
            alert("Failed to create post");
        }
    } catch (err) {
        console.error("Error creating post:", err.response?.data || err.message);
        alert(`Error: ${err.response?.data?.detail || err.message}`);
    }
};

No authentication class specified.

Add the authentication_classes variable to PostListCreateView and place the authentication class in the array to use.

Perhaps, given that the token type delivered from axios is Bearer, it is expected to use jwt tokens.

If you have implemented JWT token logic through the simple-jwt library, you have to add JWTAuthentication in authentication_classes array.

from rest_framework_simplejwt.authentication import JWTAuthentication

class PostListCreateView(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    authentication_classes=[JWTAuthentication]

For your information, 401 error seems to occur during the Permission process.

If you do not specify authentication_classes, as in your case, identify them as AnonymousUser and perform a permission check.

You must accurately identify the user who sent the request through the authentication class to perform a permission check.

Back to Top