tayfunk
tayfunk

Reputation: 1

Django Rest Framework Authentication with HTML Form

I'm new to backend and started learning djangorestframework. I followed a few tutorials and a question came to my mind. In the example below I use token authentication. With Swagger UI, I can authenticate without any problems and perform crud operations on todo items.

I thought about what it would be like if I started building up the frontend of this project and created a login form that would authenticate the user when he entered user crendentials.

I tried a few times but I couldn't do it. With Swagger UI, I send a request to the api/user/token endpoint and I can authenticate with the token I receive. But I cannot achieve this with the login form I created.

app/user/views.py:

"""
Views for the user API.
"""
from rest_framework import generics, authentication, permissions
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.settings import api_settings

from user.serializers import (
    UserSerializer,
    AuthTokenSerializer,
)


class CreateUserView(generics.CreateAPIView):
    """Create a new user in the system."""
    serializer_class = UserSerializer


class CreateTokenView(ObtainAuthToken):
    """Create a new auth token for user."""
    serializer_class = AuthTokenSerializer
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES


class ManageUserView(generics.RetrieveUpdateAPIView):
    """Manage the authenticated user."""
    serializer_class = UserSerializer
    authentication_classes = [authentication.TokenAuthentication]
    permission_classes = [permissions.IsAuthenticated]

    def get_object(self):
        """Retrieve and return the authenticated user."""
        return self.request.user

app/user/urls.py:

"""
URL mappings for the user API.
"""
from django.urls import path

from user import views


app_name = 'user'

urlpatterns = [
    path('create/', views.CreateUserView.as_view(), name='create'),  # create user
    path('token/', views.CreateTokenView.as_view(), name='token'),
    path('me/', views.ManageUserView.as_view(), name='me'),  # get user
]

app/user/serializers.py:

"""
Serializers for the user API View.
"""
from django.contrib.auth import (
    get_user_model,
    authenticate,
)
from django.utils.translation import gettext as _
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    """Serializer for the user object."""

    class Meta:
        model = get_user_model()
        fields = ['email', 'password', 'name']
        extra_kwargs = {'password': {'write_only': True, 'min_length': 5}}

    def create(self, validated_data):
        """Create and return a user with encrypted password."""
        return get_user_model().objects.create_user(**validated_data)

    def update(self, instance, validated_data):
        """Update and return user."""
        password = validated_data.pop('password', None)
        user = super().update(instance, validated_data)

        if password:
            user.set_password(password)
            user.save()

        return user


class AuthTokenSerializer(serializers.Serializer):
    """Serializer for the user auth token."""
    email = serializers.EmailField()
    password = serializers.CharField(
        style={'input_type': 'password'},
        trim_whitespace=False,
    )

    def validate(self, attrs):
        """Validate and authenticate the user."""
        email = attrs.get('email')
        password = attrs.get('password')
        user = authenticate(
            request=self.context.get('request'),
            username=email,
            password=password,
        )
        if not user:
            msg = _('Unable to authenticate with provided credentials.')
            raise serializers.ValidationError(msg, code='authorization')

        attrs['user'] = user
        return attrs

app/todo/views.py:

"""
Views for the todo APIs
"""
from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication  # noqa
from rest_framework.permissions import IsAuthenticated  # noqa
from django.http.response import HttpResponse

from core.models import Todo
from todo import serializers
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer


class TodoViewSet(viewsets.ModelViewSet):
    """View for manage todo APIs."""
    serializer_class = serializers.TodoDetailSerializer
    queryset = Todo.objects.all()

    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    renderer_classes = [TemplateHTMLRenderer, JSONRenderer]
    template_name = 'partials/todo.html'

    def get_queryset(self):
        """Retrieve todos for authenticated user."""
        return self.queryset.filter(user=self.request.user).order_by('created_at')

    def get_serializer_class(self):
        """Return the serializer class for request."""
        if self.action == 'list':
            return serializers.TodoSerializer

        return self.serializer_class

    def create(self, request, *args, **kwargs):
        todo = self.get_serializer(data=request.data)
        todo_count = self.get_queryset().count()
        todo.is_valid(raise_exception=True)
        self.perform_create(todo)
        if request.accepted_renderer.format == 'html':
            return Response({"todo": todo.data, "todo_count": todo_count})
        return Response(todo.data)

    def perform_create(self, serializer):
        """Create a new todo."""
        serializer.save(user=self.request.user)

    def partial_update(self, request, *args, **kwargs):
        instance = self.get_object()
        instance.status = not instance.status
        instance.save()
        todo = self.get_serializer(instance)
        if request.accepted_renderer.format == 'html':
            return Response({"todo": todo.data})
        return Response(todo.data)

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return HttpResponse()

    def perform_destroy(self, instance):
        instance.delete()

Simple login form:


<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form id="loginForm" hx-post="/api/login/">
        {% csrf_token %}
        <!-- Form fields for email and password -->
        <label for="email">Email:</label><br>
        <input type="email" id="email" name="email"><br>
        <label for="password">Password:</label><br>
        <input type="password" id="password" name="password"><br><br>
        <!-- Submit button -->
        <button type="submit">Login</button>
    </form>

    <script>
    </script>
</body>
</html>

With this form, I want to obtain a token by sending a request with user credentials to the api/user/token endpoint and authenticate the user with this token.

I've been looking for the answer for a few days, but I couldn't find it. I apologize if the question has already been asked.

I defined a view to the api/user/token path and rendered it in the view. But I got a "method not allowed" error.

I tried to fetch the token with a js script and sent a request by adding the token to the header, but this did not yield any results either.

Upvotes: 0

Views: 52

Answers (0)

Related Questions