🐛 Частые ошибки: блок Auth

🎯 Типичные проблемы при работе с Auth, JWT, Signals К оглавлению урока

⚡ Топ-3 ошибки

  1. Забыть 'rest_framework.authtoken' в INSTALLED_APPS → Token таблица не создана.
  2. Не зарегистрировать сигналы в AppConfig.ready() → обработчики не вызываются.
  3. Хранить JWT в localStorage → уязвимость XSS. Используй httpOnly-куки.

Аутентификация и токены

1. rest_framework.authtoken не добавлен в INSTALLED_APPS

Проблема

# Забыли добавить authtoken
INSTALLED_APPS = ['django.contrib.auth', 'rest_framework']
# Ошибка: django.db.utils.OperationalError: no such table: authtoken_token

Исправление

INSTALLED_APPS = [
    'django.contrib.auth',
    'rest_framework',
    'rest_framework.authtoken',  # добавить!
]
# + python manage.py migrate

2. Неверный формат заголовка Authorization

Проблема

# TokenAuthentication — неверно:
Authorization: Bearer abc123   # DRF ожидает "Token", не "Bearer"

# JWTAuthentication — неверно:
Authorization: Token eyJhbGci...   # SimpleJWT ожидает "Bearer"

Исправление

# TokenAuthentication:
Authorization: Token abc123...

# JWTAuthentication (SimpleJWT):
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

3. JWT в localStorage вместо httpOnly-куки

Проблема (небезопасно)

// XSS может украсть токен!
localStorage.setItem('access_token', data.access);

Исправление

# Сервер: httpOnly-куки недоступны JavaScript
response.set_cookie(
    'access_token',
    value=str(access_token),
    httponly=True,   # JS не может прочитать
    secure=True,     # только HTTPS
    samesite='Lax',  # защита от CSRF
)

Разрешения

4. has_object_permission() не вызывается автоматически

Проблема

# Разработчик думает, что has_object_permission() проверяется всегда
# Но при ListAPIView get_queryset() не вызывает get_object()
# → has_object_permission() НЕ вызывается для списка

Исправление

# has_object_permission() вызывается ТОЛЬКО при get_object()
# (RetrieveAPIView, UpdateAPIView, DestroyAPIView)

# Для ListAPIView фильтруй queryset в get_queryset():
def get_queryset(self):
    return Book.objects.filter(owner=self.request.user)

5. read_only_fields не установлено для owner

Проблема

# Пользователь может подменить owner в теле запроса!
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'  # owner включён и изменяем

Исправление

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'
        read_only_fields = ['owner']  # нельзя изменить через API

6. Забыть выполнить migrate после добавления Meta.permissions

Проблема

# Добавили permissions в Meta, но не сделали migrate
class Genre(models.Model):
    class Meta:
        permissions = [('can_get_statistic', 'Can get genres statistic')]
# request.user.has_perm('first_app.can_get_statistic') → всегда False!

Исправление

python manage.py makemigrations
python manage.py migrate
# Теперь разрешение появится в Django Admin → Permissions

Сигналы

7. Сигналы не регистрируются — не импортированы в ready()

Проблема

# apps.py — забыли импортировать сигналы
class FirstAppConfig(AppConfig):
    name = 'first_app'
    # ready() не переопределён → сигналы не подключены → обработчики не вызываются

Исправление

class FirstAppConfig(AppConfig):
    name = 'first_app'
    def ready(self):
        import first_app.signals  # обязательно!

8. Бесконечный цикл в post_save сигнале

Проблема

@receiver(post_save, sender=Book)
def update_book(sender, instance, **kwargs):
    instance.title = instance.title.strip()
    instance.save()  # снова вызывает post_save → бесконечный цикл!

Исправление

# Вариант 1: update без сохранения через ORM
@receiver(post_save, sender=Book)
def update_book(sender, instance, **kwargs):
    Book.objects.filter(pk=instance.pk).update(title=instance.title.strip())

# Вариант 2: использовать pre_save (до сохранения)
@receiver(pre_save, sender=Book)
def clean_title(sender, instance, **kwargs):
    instance.title = instance.title.strip()
    # save() не нужен — это pre_save, изменения применятся автоматически

9. EMAIL_BACKEND не настроен — send_mail падает с ошибкой

Проблема

# В settings.py нет настройки EMAIL_BACKEND
# При вызове send_mail() — ConnectionRefusedError или SMTPAuthenticationError

Исправление

# Для разработки (письма выводятся в консоль):
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

# Для продакшн (реальный Gmail SMTP):
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'your@gmail.com'
EMAIL_HOST_PASSWORD = 'app_password'  # не обычный пароль — App Password Google

SimpleJWT

10. rest_framework_simplejwt.token_blacklist не добавлен для BLACKLIST_AFTER_ROTATION

Проблема

# В settings.py включили blacklist, но не добавили приложение
SIMPLE_JWT = {'BLACKLIST_AFTER_ROTATION': True}
# Ошибка: ImproperlyConfigured — rest_framework_simplejwt.token_blacklist not in INSTALLED_APPS

Исправление

INSTALLED_APPS = [
    ...
    'rest_framework_simplejwt',
    'rest_framework_simplejwt.token_blacklist',  # добавить!
]
# + python manage.py migrate

11. Swagger не видит JWT-защищённые эндпоинты корректно

Проблема

# В Swagger UI кнопка Authorize не работает с JWT
# — get_schema_view не сконфигурирован с security definitions

Исправление

schema_view = get_schema_view(
    openapi.Info(title="API", default_version='v1'),
    public=True,
    permission_classes=[permissions.AllowAny],
)
# В Swagger UI: кнопка "Authorize" → Bearer <access_token>
⚠️ Проверить по документации: конфигурация security schemes в drf-yasg может отличаться в разных версиях. Проверьте актуальную документацию drf-yasg.