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
- 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
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>
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:
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.
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