Почему POST-запрос к /api/posts/ возвращает 401 Unauthorized в Django DRF, несмотря на правильный токен в заголовках Axios?

Проблема:
У меня возникла проблема с ответом 401 Unauthorized при выполнении POST-запроса к /api/posts/ в моем бэкенде Django Rest Framework (DRF) с фронтенда React. Экземпляр Axios в React использует перехватчик для добавления токена Bearer (хранящегося в localStorage) в заголовок Authorization, но запрос все равно не проходит.

В представлении DRF используется generics.ListCreateAPIView и требуется аутентификация с помощью permissions.IsAuthenticated. POST-запрос не выполняется, хотя токен присутствует в заголовках. Я проверил конфигурации фронтенда и бэкенда, но проблема сохраняется. В качестве url-шаблона используется posts/. Но из фронтенда все url-шаблоны для api (Django app) могут быть доступны только через /api/urlpattern/.


Django DRF Backend Code

Когда я использую url-шаблон из проекта urls.py, я могу создать пост -

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)

Но когда я использую urls.py для api, он выдает 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}`);
    }
};

Не указан класс аутентификации.

Добавьте переменную authentication_classes в PostListCreateView и поместите класс аутентификации в массив для использования.

Возможно, учитывая, что тип токена, передаваемого из axios, - Bearer, ожидается, что он будет использовать jwt-токены.

Если вы реализовали логику работы с токенами JWT с помощью библиотеки simple-jwt, вам необходимо добавить JWTAuthentication в массив authentication_classes.

from rest_framework_simplejwt.authentication import JWTAuthentication

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

Для справки, 401 ошибка, похоже, возникает в процессе получения разрешения.

Если вы не указали классы аутентификации (authentication_classes), как в вашем случае, определите их как AnonymousUser и выполните проверку прав доступа.

Вы должны точно идентифицировать пользователя, отправившего запрос, через класс аутентификации, чтобы выполнить проверку разрешения.

Вернуться на верх