Implementing Django email verification

Django offers almost the entire user management logic of a typical website out of the box. User login, logout, change password, reset the password, all of these are ready as soon as you start a new project.

But I said "almost" because there's one thing that's needed almost in every website that's not offered in Django: email verification. Fear not though. The functionality might not be offered out-of-the-box but all the required components are there.

The token

The most crucial bit for the email verification flow is the verification token. The purpose of this token is to encode the user's current state. We want it to be valid only if the user is inactive. When the user is activated, the token should no longer be valid.

Thankfully, Django creates similar tokens when sending password reset emails. We can reuse that class to create the tokens for the email activation as well.

The only difference is what elements we use to generate the token. Since we want the tokens to be valid only when the user is not yet activated, we will include user.is_active.

class EmailVerificationTokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self, user, timestamp):
        return (
                str(user.is_active) + str(user.pk) + str(timestamp)
        )


email_verification_token = EmailVerificationTokenGenerator()
email_verification_token_generator.py

Verification email

The next step is to send the verification email.

Note that we need 3 things to construct the activation URL:

  1. The current domain
  2. The user id, which we are encoding using Base64
  3. The token we discussed in the previous section  
class RegistrationView(FormView):
    # [...]


    def _send_email_verification(self, user: CustomUser):
        current_site = get_current_site(self.request)
        subject = 'Activate Your Account'
        body = render_to_string(
            'emails/email_verification.html',
            {
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                'token': email_verification_token.make_token(user),
            }
        )
        EmailMessage(to=[user.email], subject=subject, body=body).send()
registration.py
<div>
    <a href=
    "http://{{ domain }}{% url 'activate' uidb64=uid token=token %}">
    Please activate your account</a>
</div>
email_verification.html

Activate the user

The last step is to activate the user. We do the reverse process we did in the previous step.

  1. Base64 decode to get the user id
  2. Fetching the user using the user id
  3. Using the EmailVerificationTokenGenerator to verify that the token is still valid

If the token is valid, which means the user is not yet activated, we set the is_active to True and save. From this point on, the token will not be valid anymore. So we need to handle the case where this link is clicked again to display the appropriate message in the template.

urlpatterns = [    
    path('activate/<uidb64>/<token>', 
         ActivateView.as_view(), 
         name='activate'),
     # [...]
]
urls.py
class ActivateView(View):

    def get_user_from_email_verification_token(self, token: str):
        try:
            uid = force_str(urlsafe_base64_decode(self))
            user = get_user_model().objects.get(pk=uid)
        except (TypeError, ValueError, OverflowError,
                get_user_model().DoesNotExist):
            return None

        if user is not None \
                and \
                email_verification_token.check_token(user, token):
            return user

        return None

    def get(self, request, uidb64, token):
        user = self.get_user_from_email_verification(uidb64, token)
        user.is_active = True
        user.save()
        login(request, user)
        return redirect('registration_successful')

activate.py

Hopefully, this was a quick and straightforward way to implement email verification in your Django project.

Happy coding!