Skip to content

In this project, I configure how to apply JWT API token and also Email based OTP authentication with Django Rest Framework.

Notifications You must be signed in to change notification settings

mrshakil015/Django-JWT-API-Token-Authentication

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 

Repository files navigation

Django JWT API Token and Email OTP Authentication

Context

Project Setup

  1. At first Create Environment:
    python-m venv env
  2. After Create envirionment activate the environment:
    .\env\Scripts\activate
  3. Install Dependencies:
    pip install -r requirements.txt
  4. Create a Djnago Project:
    django-admin startproject djangojwt
  5. Change current directory to djangojwt folder:
    cd djangojwt
  6. Create a Django App:
    django-admin startapp myapp
  7. Update settings.py to Add rest_framework and app_name to INSTALLED_APPS:
    INSTALLED_APPS = [
    ......
    ......
    'rest_framework',
    'myapp',
    ]

⬆️ Go to Context

Configure JWT Token

  • Install Required Libraries

    pip install djangorestframework-simplejwt
  • Update settings.py to Add rest_framework_simplejwt inside the INSTALLED_APPS:

      INSTALLED_APPS = [
      ......
      ......
      'rest_framework',
      'rest_framework_simplejwt',
      ]
  • Then, your django project must be configured to use the library. In settings.py, add rest_framework_simplejwt.authentication.JWTAuthentication to the list of authentication classes:

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework_simplejwt.authentication.JWTAuthentication',
        )
    }

⬆️ Go to Context

Final Setup

  • After Implement the Model Migrate the database:
    py manage.py makemigrations
    py manage.py migrate
  • Create superuser
    py manage.py createsuperuser
  • Run the project
    py manage.py runserver

⬆️ Go to Context

Create Serializer Class:

UserSerializer

To configure the serializer create serializers.py file inside the app:

  • First create UserSerializer to get or store the user data:
    from rest_framework import serializers
    from django.contrib.auth.models import User
    
    class UserSerializer(serializers.ModelSerializer):
        class Meta:
            model = User
            fields = ['id','username','email','date_joined']

⬆️ Go to Context

UserRegisterSerializer:

  • Create UserRegisterSerializer class for register the user information:
    class UserRegisterSerializer(serializers.ModelSerializer):
        id = serializers.PrimaryKeyRelatedField(read_only=True)
        username = serializers.CharField()
        first_name = serializers.CharField()
        last_name = serializers.CharField()
        email = serializers.EmailField()
        password = serializers.CharField(write_only=True)
        confirm_password = serializers.CharField(write_only=True)
        
        class Meta:
            model = User
            fields = ['id','username','first_name','last_name','email','password','confirm_password']
            
  • To validate the username create a function inside the UserRegisterSerializer class. This function check this user already exists or not.
    from rest_framework.exceptions import ValidationError
    def validate_username(self, username):
        if User.objects.filter(username=username).exists():
            detail = {
                "detail": "User Already exist!"
            }
            raise ValidationError(detail=detail)
        return username
  • To validate the password matching and also check email already exists or not.
    def validate(self, instance):
        if instance['password'] != instance['confirm_password']:
            raise ValidationError({"message":"Both password must match"})
        if User.objects.filter(email=instance['email']).exists():
            raise ValidationError({"message":"Email already taken!"})
        return instance
  • After validate the user then implement a functio name create to create the user.
    def create(self, validated_data):
        password = validated_data.pop('password')
        confirm_password = validated_data.pop('confirm_password')
        user = User.objects.create(**validated_data)
        user.set_password(password)
        user.save()
        return user

⬆️ Go to Context

UserLoginSerializer:

  • Create user login serializer
    class UserLoginSerializer(serializers.ModelSerializer):
        username = serializers.CharField()
        password = serializers.CharField(write_only=True)
        
        class Meta:
            model = User
            fields = ['username', 'password']

⬆️ Go to Context

UserLoginOTPSerializer:

  • Create user login otp serializer
    class UserLoginOTPSerializer(serializers.Serializer):
        username = serializers.CharField()
        otp = serializers.CharField(max_length=6)

⬆️ Go to Context

Create View Methodology

Create viewsets using diffent api view like: ModelViewSet, GenericAPIView, APIView method. Also import required libries:

User Register View

  • Here I create user registration method using ModelViewSet with the custom @action method for custom registration endpoint.
    #Import the libries
    from django.contrib.auth.models import User
    from rest_framework import status,viewsets
    from rest_framework.exceptions import MethodNotAllowed
    from .serializers import UserRegisterSerializer
    class UserRegisterViewSet(viewsets.ModelViewSet):
        queryset = User.objects.all()
        serializer_class = UserRegisterSerializer
  • ModelViewSet provide default create method. But here i need to customize the registration that's why apply MethodNotAllowed. For that include the create function under the UserRegisterViewSet class:
    def create(self, request, *args, **kwargs):
        raise MethodNotAllowed(method="POST",detail="User creation is not allowed on this endpoint. Go to '/users/register/' endpoint")
  • Now apply the custom registratio function under the UserRegisterViewSet class:
    @action(methods=['post'], detail=False, url_path='register')
    def register(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            user = serializer.save()
            response = {
                'success': True,
                'user': {
                    'id': user.id,
                    'username': user.username,
                    'email': user.email,
                    'first_name': user.first_name,
                    'last_name': user.last_name,
                }
            }
            return Response(response, status=status.HTTP_201_CREATED)
        raise ValidationError(serializer.errors, code=status.HTTP_400_BAD_REQUEST)

⬆️ Go to Context

User Login View:

  • Here include the user login view. With the login functionalities include the user JWT Authentication and Email based 2FA OTP authentication. Step by step process how a user can access their data into their dashboard or account:
    • Goto /api/login/ endpoint and Login using username and password
    • After that send a OTP user registered email address.
    • To validate the OTP goto /api/verify-otp/ endpoint and verify the otp using username and otp.
    • After validation generate a refresh token and access token. Using the access token user can access their data.
  • Before create login method configure the email configuration. Show the configuration here: Click Here
  • Now create Loginview method. At first need to import some libries:
    from rest_framework.exceptions import ValidationError
    Create an in-memory dictionary otp_storage to stored the OTP temporarily in an in-memory dictionary. It declare globally:
    otp_storage = {}
    class LoginView(generics.GenericAPIView):
    serializer_class = UserLoginSerializer
    
    def post(self, request, *args, **kwargs):
        username = request.data.get("username")
        password = request.data.get("password")
    
        if not username or not password:
            raise ValidationError({"message": "Username and password are required."})
    
        # Authenticate the user
        user = authenticate(username=username, password=password)
        if user is None:
            raise ValidationError({"message": "Invalid credentials"})
    
        # Generate opt
        otp = get_random_string(length=6, allowed_chars='1234567890')
        otp_storage[username] = otp
    
        # Send OTP to user's email
        send_mail(
            subject="Your OTP for Login",
            message=f"Your OTP is {otp}. It is valid for 5 minutes.",
            from_email="[email protected]",
            recipient_list=[user.email],
        )
    
        return Response({
            "message": "OTP sent to your registered email address."
            },
            status=status.HTTP_200_OK
        )

OTP Verify View:

  • This function used of verify the otp.
    class VerifyOtpView(generics.GenericAPIView):
    serializer_class = UserLoginOTPSerializer
    
    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        # Extract validated data
        username = serializer.validated_data.get('username')
        otp = serializer.validated_data.get('otp')
    
        # Check if OTP exists for the username and validate it
        if username not in otp_storage:
            raise ValidationError({"message": "OTP has not been sent or expired or username not valid."})
        
        # Validate OTP
        if otp_storage[username] != otp:
            raise ValidationError({"message": "Invalid OTP."})
    
        # Clean up OTP after verification
        del otp_storage[username]
    
        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            raise ValidationError({"message": "User does not exist."})
    
        refresh = RefreshToken.for_user(user)
        access_token = str(refresh.access_token)
        user_serializer = UserSerializer(user)
    
    
        return Response(
            {
                'access': access_token,
                'refresh': str(refresh),
                'user': user_serializer.data,
            },
            status=status.HTTP_200_OK
        )

⬆️ Go to Context

Email Configuration:

  • Install python-decouple.It is a popular package for managing environment variables in Django.
    pip install python-decouple
  • Create a .env file in the root directory of your project (where manage.py is located). This file will store your sensitive data.
    EMAIL_HOST=smtp.gmail.com
    EMAIL_PORT=587
    EMAIL_USE_TLS=True
    EMAIL_USE_SSL=False
    EMAIL_HOST_USER=your-email@gmail.com
    EMAIL_HOST_PASSWORD=your-email-app-password
  • Now how to get the email EMAIL_HOST_PASSWORD:
    • Go to the Manage Google Account
    • If 2FA not enable at first enable the 2FA
    • Then Search App Passwords. And go to this page.
    • Set any app name like: (e.g. Django App etc.)
    • After that click create. Then it provide a 16-characters password. like this: takv dcgx ldzo poll.
    • Then copy it and and using on the project.
  • Configure Email setting inside the settings.py:
    from decouple import config
    
    EMAIL_HOST = config('EMAIL_HOST', default='smtp.gmail.com')
    EMAIL_PORT = config('EMAIL_PORT', default=587, cast=int)
    EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=True, cast=bool)
    EMAIL_USE_SSL = config('EMAIL_USE_SSL', default=False, cast=bool)
    EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='')
    EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
    If fetch any error using from decouple import config this package. Then used bellow method:
    from dotenv import load_dotenv
    import os
    
    # Email configuration from environment variables
    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
    
    EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp.gmail.com')
    EMAIL_PORT = int(os.getenv('EMAIL_PORT', 587))
    EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS', 'True') == 'True'
    EMAIL_USE_SSL = os.getenv('EMAIL_USE_SSL', 'False') == 'True'
    EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', '')
    EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', '')
  • Sending Email views:
    from django.core.mail import send_mail
    from django.conf import settings
    
    def send_otp_email(user, otp):
        send_mail(
            subject="Your OTP for Login",
            message=f"Your OTP is {otp}. It is valid for 5 minutes.",
            from_email=settings.EMAIL_HOST_USER,
            recipient_list=[user.email],
        )

⬆️ Go to Context

About

In this project, I configure how to apply JWT API token and also Email based OTP authentication with Django Rest Framework.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages