How can I implement email verification in Django
Completely stumped! I'm using the console as my email backend. I end up with False
in token_generator.check_token
as a result "Invalid or expired token." is displayed in my homepage when I navigate to say "http://localhost:8000/user/verify-email/?token=cgegv3-ec1fe9eb2cebc34e240791d72fb10d7d&email=test16@example.com"
Here's my code
from django.contrib.auth.tokens import PasswordResetTokenGenerator
class CustomPasswordResetTokenGenerator(PasswordResetTokenGenerator):
pass
# Define a single instance of the token generator
token_generator = CustomPasswordResetTokenGenerator()
def verify_email(request):
email = request.GET.get("email")
token = request.GET.get("token")
try:
user = CustomUser.objects.get(email=email)
except CustomUser.DoesNotExist:
messages.error(request, "Invalid verification link.")
return redirect("home")
if token_generator.check_token(user, token):
user.is_active = True
user.save()
messages.success(request, "Your email has been verified!")
return redirect("sign_in")
else:
messages.error(request, "Invalid or expired token.")
return redirect("home")
from django.core.mail import send_mail
from django.urls import reverse
from user_management.utils import token_generator
def send_verification_email(user, request):
token = token_generator.make_token(user)
verification_url = request.build_absolute_uri(
reverse("verify_email") + f"?token={token}&email={user.email}"
)
send_mail(
"Verify your email",
f"Click the link to verify your email: {verification_url}",
"no-reply@example.com",
[user.email],
fail_silently=False,
)
The best way for that is to using Django Allauth, You can do anything with that secure and easy, For sure Allauth have many other options for you.
The code you posted seems fine. The check_token method performs several checks and figuring out which exact one fails should lead you to a solution. You can add breakpoints or print statements in place where the Django package is installed or bring the code into your project. Since you're already subclassing PasswordResetTokenGenerator
you can:
from django.conf import settings
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils.crypto import constant_time_compare
from django.utils.http import int_to_base36
class CustomPasswordResetTokenGenerator(PasswordResetTokenGenerator):
def check_token(self, user, token):
"""
Check that a password reset token is correct for a given user.
"""
# Use breakpoints / print statements to figure out
# which of the conditions fails here and where to go next
if not (user and token):
return False
# Parse the token
try:
ts_b36, _ = token.split("-")
except ValueError:
return False
try:
ts = base36_to_int(ts_b36)
except ValueError:
return False
# Check that the timestamp/uid has not been tampered with
for secret in [self.secret, *self.secret_fallbacks]:
if constant_time_compare(
self._make_token_with_timestamp(user, ts, secret),
token,
):
break
else:
return False
# Check the timestamp is within limit.
if (self._num_seconds(self._now()) - ts) > settings.PASSWORD_RESET_TIMEOUT:
return False
return True
# Define a single instance of the token generator
token_generator = CustomPasswordResetTokenGenerator()