Ruddra.com

Implementation of Forgot/Reset Password Feature in Django

Implementation of Forgot/Reset Password Feature in Django
Photo by Nikita Kostrykin on Unsplash

Django has its own implementation for reset/forgot password for its admin site. We are going to use that code as reference to implement similar feature for a non admin-site authentication page. Although there are tons of good packages which will allow user to use their password resetting system. But if the system isn’t too complex and doesn’t need such authentication plugins, then reusing the django’s very own implementation can be a good option.

This implementation is going to divided into two parts. First part is sending an email with reset url, and the Second part is clicking the reset url attached in email and entering new password for reset completion.

Before starting anything, lets look at the django’s reset/forgot password’s implementation in django/contrib/auth/forms.py) and django/contrib/auth/views.py).

Implementation of sending an email for forgot password with reset url

We are going to make a reset password form where we are going to add an text field which will take either username or email address associated with the corresponding user.

from django import forms

class PasswordResetRequestForm(forms.Form):
    email_or_username = forms.CharField(label=("Email Or Username"), max_length=254)

We are going to make a view which will check the input email/username and send an email to user’s email address(implementation reference: github source).

from django.contrib.auth.tokens import default_token_generator
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.template import loader
from django.core.mail import send_mail
from settings import DEFAULT_FROM_EMAIL
from django.views.generic import FormView
from .forms import PasswordResetRequestForm
from django.contrib import messages
from django.contrib.auth.models import User
from django.db.models.query_utils import Q

class ResetPasswordRequestView(FormView):
    template_name = "account/test_template.html"
    success_url = '/account/login'
    form_class = PasswordResetRequestForm

    def form_valid(self, *args, **kwargs):
        form = super(ResetPasswordRequestView, self).form_valid(*args, **kwargs)
        data= form.cleaned_data["email_or_username"]
        user= User.objects.filter(Q(email=data)|Q(username=data)).first()
        if user:
            c = {
                'email': user.email,
                'domain': self.request.META['HTTP_HOST'],
                'site_name': 'your site',
                'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                'user': user,
                'token': default_token_generator.make_token(user),
                'protocol': self.request.scheme,
            }
            email_template_name='registration/password_reset_email.html'
            subject = "Reset Your Password"
            email = loader.render_to_string(email_template_name, c)
            send_mail(subject, email, DEFAULT_FROM_EMAIL , [user.email], fail_silently=False)
        messages.success(self.request, 'An email has been sent to ' + data +" if it is a valid user.")
        return form

As you see above, the code is fairly simple(although it looks long). An encoded user id has been generated here and a token by using. This user id is going to be used later to get the user, the token will be used for checking validity of the url for that user and both the token and the user id is going to be used as unique reference for reset password url. c is a dictionary which has user id, token and other related data etc. This dictionary is going to be blent with the template registration/password_reset_email.html and send to the user’s email address. We will be using django’s message framework to show messages.

{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li>{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
    {% endfor %}
    {% endif %}
</ul>

<form action="" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Submit" />
</form>

Two more things before wrapping up sending email part. One, making a url for using this view.

urlpatterns = patterns(
    url(r'^account/reset_password', ResetPasswordRequestView.as_view(), name="reset_password"),
)

Two, create or edit the template inside TEMPLATES directory named registration/password_reset_email.html.

{% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}{%endblocktrans %}

{% trans "Please go to the following page and choose a new password:" %}
    {% block reset_link %}
        {{ domain }}{% url 'reset_password_confirm' uidb64=uid token=token %}
    {% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
{% trans "Thanks for using our site!" %}
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
{% endautoescape %}

Now run the server and you will see forms like the screen shots below: (This screenshots look cool because django adminsite’s js/css have been used here.)

Screenshots password reset flow

Rendered template from PasswordResetRequestForm form

django reset

Rendered template from PasswordResetRequestForm form with error messages

django reset

Rendered template of login form with sent email confirmation message

django reset

Sent email look

django reset

Implementation of clicking the reset url and entering new password for reset completion

First, lets write a form which will have two fields new password and retype password field.

class SetPasswordForm(forms.Form):
    error_messages = {
        'password_mismatch': ("The two password fields didn't match."),
        }
    new_password1 = forms.CharField(label=("New password"), required=True,
                                    widget=forms.PasswordInput)
    new_password2 = forms.CharField(label=("New password confirmation"), required=True,
                                    widget=forms.PasswordInput)

    def clean_new_password2(self):
        password1 = self.cleaned_data.get('new_password1')
        password2 = self.cleaned_data.get('new_password2')
        if password1 != password2:
            raise forms.ValidationError(
                self.error_messages['password_mismatch'],
                code='password_mismatch',
            )
        return password2

It will take two password input and verify if they match, if those inputs match(in clean method), it will return password. Now using that form, we are going to write a view(reference for implementation).

class PasswordResetConfirmView(FormView):
    template_name = "account/test_template.html"
    success_url = '/admin/'
    form_class = SetPasswordForm

    def form_valid(self, *arg, **kwargs):
        form = super(PasswordResetConfirmView, self).form_valid(*arg, **kwargs)
        uidb64=self.kwargs['uidb64']
        token=self.kwargs['token']
        UserModel = get_user_model()
        try:
            uid = urlsafe_base64_decode(uidb64)
            user = User._default_manager.get(pk=uid)
        except (TypeError, ValueError, OverflowError, User.DoesNotExist):
            user = None

        if user is not None and default_token_generator.check_token(user, token):
            new_password= form.cleaned_data['new_password2']
            user.set_password(new_password)
            user.save()
            messages.success(self.request, 'Password reset has been successful.')
        else:
            messages.error(self.request, 'Password reset has not been unsuccessful.')
        return form

URL for this view:

urlpatterns += patterns(
    url(r'^account/reset_password_confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', PasswordResetConfirmView.as_view(), name='reset_password_confirm'),
)

Well PasswordResetConfirmView takes two parameter from urls, uidb64 and token, those were sent within email generated by ResetPasswordRequestView. We got user id hence the user by decoding uid64, and function default_token_generator checks the token against the user. If they are valid, then we set new password for the user. If they are not valid, it will show an error message saying the url is no longer valid.

Screenshots reset successful

Rendered template for SetPasswordForm form

django reset

Reset successful

django reset

Alternative reference to this implementation

You can check the django authentication views for your own implementation or customize them.

In conclusion

Thus you implement your very own forgot or reset the password. For full project/implementation, please check/fork this repository. If you have any questions, please share in the comment section below.

Last updated: Dec 03, 2020


Share Your Thoughts
M↓ Markdown