Python Password Reset Via Email Tutorial

Written by

in

Welcome to this step‑by‑step Python password reset via email tutorial. Whether you’re building a small Flask app or a full‑featured Django site, offering a secure, user‑friendly password reset flow is essential for both user satisfaction and security compliance. In this guide you’ll learn how to generate a one‑time token, send a reset link through email, and safely update the user’s password—all with clean, reusable Python code.

Why a Password‑Reset Feature Matters

Users forget passwords, and a robust password reset via email is the most common recovery method. Implementing it correctly protects against:

  • Credential stuffing attacks.
  • Unauthorized account access.
  • Compliance violations (GDPR, CCPA, etc.).

By following this tutorial you’ll avoid common pitfalls such as plain‑text token storage, insecure link handling, and email spoofing.

Prerequisites

Before diving into code, make sure you have the following ready:

  • Python 3.9+ installed.
  • A virtual environment (recommended venv or conda).
  • Basic knowledge of Flask or Django (the tutorial covers both).
  • Access to an SMTP server (Gmail, SendGrid, Mailgun, etc.).

Project Setup

1. Create a new virtual environment

python -m venv venv
source venv/bin/activate   # Linux/macOS
venv\Scripts\activate      # Windows

2. Install required packages

We’ll use Flask for the lightweight example and itsdangerous for token generation. For Django, the same token library works out of the box.

pip install Flask itsdangerous Flask-Mail
# For Django users:
pip install Django

Configuring the Email Backend

Both Flask and Django need SMTP credentials. Store them securely in environment variables to keep them out of source control.

# .env (example)
MAIL_SERVER=smtp.gmail.com
MAIL_PORT=587
MAIL_USE_TLS=True
MAIL_USERNAME=your.email@gmail.com
MAIL_PASSWORD=your_app_password
SECRET_KEY=super‑secret‑key‑for‑tokens

Load these variables in your app using python-dotenv or the OS module.

Generating a Secure Token

The core of a Python password reset via email tutorial is a time‑limited token that can be verified later. itsdangerous.URLSafeTimedSerializer provides exactly that.

from itsdangerous import URLSafeTimedSerializer
import os

def get_serializer():
    secret_key = os.getenv('SECRET_KEY')
    return URLSafeTimedSerializer(secret_key)

def generate_reset_token(email):
    s = get_serializer()
    return s.dumps(email, salt='password-reset-salt')

def verify_reset_token(token, max_age=3600):
    s = get_serializer()
    try:
        email = s.loads(token, salt='password-reset-salt', max_age=max_age)
    except Exception:
        return None
    return email

Flask Implementation

Application Skeleton

from flask import Flask, render_template, request, redirect, url_for, flash
from flask_mail import Mail, Message
import os

app = Flask(__name__)
app.config.update(
    SECRET_KEY=os.getenv('SECRET_KEY'),
    MAIL_SERVER=os.getenv('MAIL_SERVER'),
    MAIL_PORT=int(os.getenv('MAIL_PORT')),
    MAIL_USE_TLS=os.getenv('MAIL_USE_TLS') == 'True',
    MAIL_USERNAME=os.getenv('MAIL_USERNAME'),
    MAIL_PASSWORD=os.getenv('MAIL_PASSWORD')
)

mail = Mail(app)

Reset Request View

This endpoint accepts an email address, creates a token, and sends the reset link.

@app.route('/reset-password', methods=['GET', 'POST'])
def reset_request():
    if request.method == 'POST':
        email = request.form['email']
        # TODO: Verify email exists in your DB
        token = generate_reset_token(email)
        reset_url = url_for('reset_token', token=token, _external=True)
        msg = Message('Password Reset Request',
                      sender=app.config['MAIL_USERNAME'],
                      recipients=[email])
        msg.body = f'''Hi,

We received a request to reset your password. Click the link below to choose a new password:

{reset_url}

If you didn’t request this, simply ignore this email.
'''
        mail.send(msg)
        flash('A password reset link has been sent to your email.', 'info')
        return redirect(url_for('login'))
    return render_template('reset_request.html')

Reset Token View

The user lands on this page after clicking the link. We verify the token and allow a new password to be set.

@app.route('/reset-password/<token>', methods=['GET', 'POST'])
def reset_token(token):
    email = verify_reset_token(token)
    if not email:
        flash('The reset link is invalid or has expired.', 'danger')
        return redirect(url_for('reset_request'))

    if request.method == 'POST':
        new_password = request.form['password']
        # TODO: Hash the password and update the user record
        flash('Your password has been updated!', 'success')
        return redirect(url_for('login'))

    return render_template('reset_token.html', token=token)

Django Implementation

If you prefer Django, the built‑in PasswordResetView handles most of the heavy lifting. Below is a minimal setup that still follows the same token logic.

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('reset/', views.PasswordResetRequestView.as_view(), name='password_reset'),
    path('reset///', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
]

views.py

from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.models import User
from django.core.mail import send_mail
from django.shortcuts import render, redirect
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes, force_str
from django.views import View
import os

class PasswordResetRequestView(View):
    def get(self, request):
        return render(request, 'password_reset_form.html')

    def post(self, request):
        email = request.POST['email']
        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist:
            # For security, don’t reveal if the email exists
            pass
        else:
            token = default_token_generator.make_token(user)
            uid = urlsafe_base64_encode(force_bytes(user.pk))
            reset_link = request.build_absolute_uri(
                f'/reset/{uid}/{token}/')
            send_mail(
                subject='Password Reset',
                message=f'Click the link to reset your password: {reset_link}',
                from_email=os.getenv('MAIL_USERNAME'),
                recipient_list=[email],
            )
        return redirect('login')

Confirm View

class PasswordResetConfirmView(View):
    def get(self, request, uidb64, token):
        try:
            uid = force_str(urlsafe_base64_decode(uidb64))
            user = User.objects.get(pk=uid)
        except (User.DoesNotExist, ValueError, TypeError):
            user = None

        if user and default_token_generator.check_token(user, token):
            return render(request, 'password_reset_confirm.html', {'validlink': True, 'uidb64': uidb64, 'token': token})
        else:
            return render(request, 'password_reset_confirm.html', {'validlink': False})

    def post(self, request, uidb64, token):
        # Same validation as GET
        # Update password
        new_password = request.POST['password']
        user.set_password(new_password)
        user.save()
        return redirect('login')

Security Best Practices

  • Never store plain‑text passwords. Always hash with bcrypt or argon2.
  • Limit token lifetime (commonly 1 hour) using the max_age argument.
  • Rate‑limit reset requests to prevent enumeration attacks.
  • Use HTTPS for every endpoint that handles tokens or passwords.
  • Include a short, random nonce in the email body to thwart replay attacks.

Testing the Flow

Before deploying, run through these checks:

  1. Submit a valid email – verify you receive a correctly formatted reset link.
  2. Click the link after the token expires – ensure the user sees an expiration message.
  3. Attempt to reuse a token – it should be invalid after the first successful password change.
  4. Submit a weak password – enforce your password policy and return clear error messages.

Deploying to Production

When you push your code live, remember to:

  • Set MAIL_USE_TLS=True or MAIL_USE_SSL=True depending on your provider.
  • Configure a dedicated email domain (e.g., no-reply@yourdomain.com) to improve deliverability.
  • Enable SPF, DKIM, and DMARC records for your sending domain.
  • Monitor bounce rates and implement a fallback email service if needed.

Conclusion

Implementing a secure Python password reset via email tutorial doesn’t have to be daunting. By leveraging itsdangerous for token handling, configuring a reliable SMTP backend, and

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *