Is this possible way to save the token in the existing table and use it for login logout change password forgot password also expirations works fine?

Should use the existing table for token to be stored and also for the reset end forget password token to be stored in the same table with expirations

To implement a secure password change mechanism that involves storing JWT tokens in the database for verification, you need to modify the previous solution to save and validate the tokens from the database.

Here's a step-by-step guide to implementing this:

Step 1: Create a Model for Storing JWT Tokens

  1. Create a new model in your Django app to store JWT tokens. This model will include fields for the token, the user it’s associated with, and its expiration status.
# models.py in your Django app
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone

class PasswordResetToken(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reset_tokens')
    token = models.CharField(max_length=255, unique=True)
    is_expired = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f'Token for {self.user.username}'

    def has_expired(self):
        # Check if the token has expired (assuming 30 minutes expiry time)
        expiry_time = self.created_at + timezone.timedelta(minutes=30)
        return timezone.now() > expiry_time

Step 2: Update the request_password_change View

Modify the request_password_change view to generate a JWT token, save it in the database, and send it via email.

# views.py in your Django app
import jwt
from django.contrib.auth.models import User
from django.contrib.auth.hashers import check_password, make_password
from django.core.mail import send_mail
from django.conf import settings
from django.http import JsonResponse, HttpResponse
from django.shortcuts import render, redirect
from django.utils import timezone
from .models import PasswordResetToken
import datetime

def request_password_change(request):
    """
    View to request a password change. It verifies the current password,
    generates a JWT token, saves it in the database, and sends it to the user's email.
    """
    if request.method == 'POST':
        email = request.POST.get('email')
        current_password = request.POST.get('current_password')

        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist:
            return JsonResponse({'error': 'User with this email does not exist.'}, status=404)

        if not check_password(current_password, user.password):
            return JsonResponse({'error': 'Current password is incorrect.'}, status=400)

        # Generate JWT token
        payload = {
            'user_id': user.id,
            'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
        }
        token = jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256')

        # Save the token in the database
        PasswordResetToken.objects.create(user=user, token=token)

        # Construct the reset URL and send email
        reset_url = request.build_absolute_uri(f"/confirm-password-change/{token}/")
        send_mail(
            subject="Password Change Request",
            message=f"Click the following link to change your password:\n{reset_url}",
            from_email=settings.EMAIL_HOST_USER,
            recipient_list=[user.email],
            fail_silently=False,
        )
        return JsonResponse({'message': 'Password change link sent to your email.'}, status=200)

    return render(request, 'request_password_change.html')

Step 3: Update the confirm_password_change View

Modify the confirm_password_change view to verify the token from the database and allow the user to change their password.

# views.py in your Django app
import jwt
from django.contrib.auth.models import User
from django.contrib.auth.hashers import make_password
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.utils import timezone
from .models import PasswordResetToken
import datetime

def confirm_password_change(request, token):
    """
    View to confirm password change using the provided JWT token.
    Verifies the token against the database and allows the user to set a new password.
    """
    try:
        # Decode the JWT token
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
        user_id = payload.get('user_id')
    except jwt.ExpiredSignatureError:
        return HttpResponse("The token has expired.", status=400)
    except jwt.InvalidTokenError:
        return HttpResponse("Invalid token.", status=400)

    # Check if the token exists in the database and is not expired
    try:
        reset_token = PasswordResetToken.objects.get(token=token, user_id=user_id)
    except PasswordResetToken.DoesNotExist:
        return HttpResponse("Invalid or used token.", status=400)

    # Check if the token has expired
    if reset_token.has_expired() or reset_token.is_expired:
        return HttpResponse("The token has expired or is already used.", status=400)

    if request.method == 'POST':
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')

        if new_password != confirm_password:
            return JsonResponse({'error': 'Passwords do not match.'}, status=400)

        # Update user's password
        user = User.objects.get(id=user_id)
        user.password = make_password(new_password)
        user.save()

        # Mark the token as expired
        reset_token.is_expired = True
        reset_token.save()

        return JsonResponse({'message': 'Password changed successfully.'}, status=200)

    return render(request, 'confirm_password_change.html', {'token': token})

Step 4: Update the URL Patterns

In your app’s urls.py, add the following URLs:

# urls.py in your Django app
from django.urls import path
from . import views

urlpatterns = [
    path('request-password-change/', views.request_password_change, name='request_password_change'),
    path('confirm-password-change/<str:token>/', views.confirm_password_change, name='confirm_password_change'),
]

Step 5: Create Templates for the Forms

  1. request_password_change.html
<!DOCTYPE html>
<html>
<head>
    <title>Request Password Change</title>
</head>
<body>
    <h2>Request Password Change</h2>
    <form method="POST">
        {% csrf_token %}
        <label for="email">Email:</label>
        <input type="email" name="email" required><br><br>
        <label for="current_password">Current Password:</label>
        <input type="password" name="current_password" required><br><br>
        <button type="submit">Send Password Change Link</button>
    </form>
</body>
</html>
  1. confirm_password_change.html
<!DOCTYPE html>
<html>
<head>
    <title>Confirm Password Change</title>
</head>
<body>
    <h2>Set a New Password</h2>
    <form method="POST">
        {% csrf_token %}
        <label for="new_password">New Password:</label>
        <input type="password" name="new_password" required><br><br>
        <label for="confirm_password">Confirm Password:</label>
        <input type="password" name="confirm_password" required><br><br>
        <button type="submit">Change Password</button>
    </form>
</body>
</html>

Step 6: Apply Migrations

Run the following commands to create and apply migrations for the new model:

python manage.py makemigrations
python manage.py migrate

How This Works:

  1. The request_password_change view:

    • Verifies the user's current password.
    • Generates a JWT token.
    • Saves the token in the PasswordResetToken model.
    • Sends an email with the token link to the user.
  2. The confirm_password_change view:

    • Decodes and verifies the JWT token.
    • Checks if the token exists in the database and is not expired.
    • Allows the user to set a new password.
    • Marks the token as expired after successful password change.

Additional Considerations:

  • Ensure SECRET_KEY is kept secure.
  • Use HTTPS in production to protect tokens and sensitive data.
  • Consider adding a background task to periodically clean up expired tokens.

This implementation ensures that tokens are stored securely in the database

Back to Top