Search code examples
djangohttpdjango-rest-frameworkserverhttp-headers

Cannot authenticate a Django user using his token in a Apache2/Ubuntu server


I am creating a Django rest framework API to interact with an Android app.

I have opened the following endpoints:

from django.urls import path, include
from .views import UserViewSet, UserProfileViewSet, CurrentUserView,LoginView 

urlpatterns = [
    path('usuarios/', UserViewSet.as_view(), name='usuarios'),
    path('usuarios-perfil/', UserProfileViewSet.as_view(), name='usuarios-perfil'),
    path('usuario-actual/', CurrentUserView.as_view(), name='usuario-actual'),
    path('login/', LoginView.as_view(), name='login'),
]

My views are these ones:

from rest_framework import generics
from django.contrib.auth.models import User
from .models import UserProfile
from .serializers import UserSerializer, UserProfileSerializer
from rest_framework.permissions import IsAuthenticated
from django.contrib.auth import get_user_model
from django.contrib.auth import authenticate
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
import logging

logger = logging.getLogger(__name__)

class UserViewSet(generics.ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

class UserProfileViewSet(generics.ListCreateAPIView):
    queryset = UserProfile.objects.all()
    serializer_class = UserProfileSerializer

class CurrentUserView(generics.RetrieveAPIView):
    permission_classes = (IsAuthenticated,) 
    serializer_class = UserSerializer
    def get_object(self):
        logger.info(self.request.META)
        logger.debug(f"CurrentUserView.get_object called for user {self.request.user}")
        return self.request.user
def authenticate(email=None, password=None):
    UserModel = get_user_model()
    try:
        user = UserModel.objects.get(email=email)
    except UserModel.DoesNotExist:
        return None

    if user.check_password(password):
        return user

# En tu LoginView
class LoginView(APIView):
    def post(self, request, format=None):
        user = authenticate(email=request.data.get('email'), password=request.data.get('password'))
        if user is not None:
            token, created = Token.objects.get_or_create(user=user)
            return Response({'token': token.key})
        else:
            return Response(status=401)

My models:

from django.db import models
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    is_premium = models.BooleanField(default=False)

    def __str__(self):
        return self.user.username

My serializers:

from rest_framework import serializers
from django.contrib.auth.models import User
from .models import UserProfile

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['username', 'is_staff']

class UserProfileSerializer(serializers.ModelSerializer):
    user = UserSerializer()

    class Meta:
        model = UserProfile
        fields = ['user', 'is_premium']

And this is my settings.py:

"""
Django settings for football_quiz_and_guide project.

Generated by 'django-admin startproject' using Django 4.2.3.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

from pathlib import Path
import os

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = 'my-secret-key'

DEBUG = True

ALLOWED_HOSTS = ['my-domain.com','my-VM-instance-IP']

# Configuraciones de seguridad
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
#SECURE_SSL_REDIRECT = True

# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'api_es',
    'rest_framework', 
    'rest_framework.authtoken',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'football_quiz_and_guide.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'football_quiz_and_guide.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'apifutbol_db',
        'USER': 'quiz-and-guide',
        'PASSWORD': 'AdGj2727',
        'HOST': '34.175.80.143',
        'PORT': '3306',
    }
}
#DATABASES = {
#    'default': {
#        'ENGINE': 'django.db.backends.mysql',
#        'NAME': 'apifutbol_db',
#        'USER': 'quizandguide',
#        'PASSWORD': 'AdGj2727',
#        'HOST': 'localhost',   # Or an IP Address that your DB is hosted on
#        'PORT': '3306',
#    }
#}
#DATABASES = {
#    'default': {
#        'ENGINE': 'django.db.backends.sqlite3',
#        'NAME': BASE_DIR / 'db.sqlite3',
#    }
#}
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ]
}

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'file': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': '/var/log/apache2/debug.log',
        },
    },
    'root': {
        'handlers': ['file'],
        'level': 'DEBUG',
    },
}





# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators

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',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/

LANGUAGE_CODE = 'es-es'

TIME_ZONE = 'Europe/Madrid'

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_URL = 'static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static/")

# Ruta base para archivos media.
MEDIA_URL = '/media/'
# Ruta absoluta en el sistema de ficheros a la carpeta que va a contener los archivos que los usuarios suben.
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')


# Media files

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

I have created my superuser with this command that also creates a Token for the user in authtoken_token table in the MySQL Database:

from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from api_es.models import UserProfile
from rest_framework.authtoken.models import Token  # Asegúrate de que estás importando el modelo Token de aquí

class Command(BaseCommand):
    help = 'Crea un nuevo usuario superusuario'

    def handle(self, *args, **options):
        username = 'lolo'
        password = 'AdGj2727'
        email = '[email protected]'

        # Crear una nueva instancia de User y guardarla
        user = User(username=username, email=email, is_active=True, is_superuser=True, is_staff=True)
        user.set_password(password)
        user.save()

        # Crear una nueva instancia de UserProfile y guardarla
        user_profile = UserProfile(user=user, is_premium=True)
        user_profile.save()

        # Crear un token para el usuario
        token = Token.objects.create(user=user)

        self.stdout.write(self.style.SUCCESS('Superusuario creado exitosamente.'))

The user is created. But when I try to access to user data:

curl -H "Authorization: Token <my-token>" https://my-domain.com/es/usuario-actual/

I get:

(myenv) C:\Users\mimur\Desktop\football_quiz_and_guide\football_quiz_and_guide>curl -H "Authorization: Token e65905ad748d67f127929c14d3a78b9de8300c51" https://football-quiz-and-guide.com/es/usuario-actual/ {"detail":"Las credenciales de autenticación no se proveyeron."}

Which means translated "Authentication credentials were not provided."

I have asked GPT4 giving it lot of info, but I don't find a solution. I have followed this tutorial step by step. When I create a token for user lolo it doesn't create a new one. It shows the same Token created while creating the superuser. But the curl request fails. I have tried with Postman and it fails too.

What can be failing and what can I do?


Solution

  • Finnaly GPT4 could realize what was hapenning.

    In my /etc/apache2/sites-available/000-default.conf file I needed to allow headers with the line:

    WSGIPassAuthorization On
    

    Then I could access to the view of my user data from a curl petition or the Android app.