A complete Django authentication system with user registration, login, logout, and password reset functionality using session-based authentication.
- User Registration with validation
- User Login/Logout
- Password Reset via Email
- Session-based authentication
- Form validation and error handling
- Secure password reset with expiration links
- Python 3.x
- Django 4.x or higher
- Email backend configuration for password reset functionality
-
Clone the repository
git clone <your-repository-url> cd <project-directory>
-
Install dependencies
pip install django
-
Add the PasswordReset model to your
models.pyfrom django.db import models from django.contrib.auth.models import User import uuid class PasswordReset(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) reset_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False) created_when = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Password reset for {self.user.username} at {self.created_when}"
-
Configure email settings in
settings.pyEMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = "smtp.gmail.com" EMAIL_PORT = 465 EMAIL_USE_SSL = True EMAIL_HOST_USER = "email@gmail.com" EMAIL_HOST_PASSWORD = "google app password"
-
Create and run migrations
python manage.py makemigrations python manage.py migrate
-
Start the development server
python manage.py runserver
your_app/ ├── models.py # PasswordReset model ├── views.py # Authentication views ├── urls.py # URL patterns └── templates/ ├── index.html # Home page ├── register.html # Registration form ├── login.html # Login form ├── forgot_password.html # Forgot password form ├── password_reset_sent.html # Reset email sent confirmation └── reset_password.html # Password reset form class PasswordReset(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) reset_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False) created_when = models.DateTimeField(auto_now_add=True)- Links to Django's built-in User model
- Uses UUID for secure reset tokens
- Tracks creation time for expiration validation
urlpatterns = [ path('', views.home, name='home'), path('register/', views.registerView, name='register'), path('login/', views.loginView, name='login'), path('logout/', views.logoutView, name='logout'), path('forgot-password/', views.forgotPassword, name='forgot-password'), path('password-reset-sent/<str:reset_id>/', views.passwordResetSent, name='password-reset-sent'), path('reset-password/<str:reset_id>/', views.resetPassword, name='reset-password'), ]@login_required def home(request): return render(request, 'index.html')This view requires user authentication and serves as the main dashboard after login.
Process:
-
User fills out registration form with:
- First name
- Last name
- Username
- Password
- Confirm password
-
Server-side validation:
- Checks if username already exists
- Checks if email already exists
- Validates password length (minimum 8 characters)
- Confirms password match
-
On success:
- Creates new user account
- Redirects to login page with success message
-
On error:
- Displays appropriate error messages
- Redirects back to registration form
Code Implementation:
def registerView(request): if request.method == "POST": first_name = request.POST.get('first_name') last_name = request.POST.get('last_name') username = request.POST.get('username') email = request.POST.get('email') password = request.POST.get('password') confirm_password = request.POST.get('confirm_password') user_data_has_error = False # checking whether email and username are not being used if User.objects.filter(username=username).exists(): user_data_has_error = True messages.error(request, 'Username already exists') if User.objects.filter(email=email).exists(): user_data_has_error = True messages.error(request, 'Email already exists') if len(password) < 8: user_data_has_error = True messages.error(request, 'Password must be at least 8 characters') if (password != confirm_password): user_data_has_error = True messages.error(request, 'Passwords do not match') if not user_data_has_error: new_user = User.objects.create_user( first_name = first_name, last_name = last_name, email = email, username = username, password = password ) messages.success(request, 'Account created. Login now') return redirect('login') return redirect('register') return render(request, 'register.html')Process:
- User enters username and password
- Django authenticates credentials using
authenticate() - On success:
- User session is created using
login() - Redirects to home page
- User session is created using
- On failure:
- Displays "Invalid username or password" error
- Redirects back to login form
Code Implementation:
def loginView(request): if request.method == "POST": # getting user inputs from frontend username = request.POST.get('username') password = request.POST.get('password') # authenticate credentials user = authenticate(request, username=username, password=password) if user is not None: login(request, user) return redirect('home') messages.error(request, 'Invalid username or password') return redirect('login') return render(request, 'login.html')Process:
- Calls Django's
logout()function - Destroys user session
- Redirects to login page
Code Implementation:
def logoutView(request): logout(request) return redirect('login')Process:
- User enters email address
- System checks if email exists in database
- If email exists:
- Creates new
PasswordResetinstance with unique UUID - Generates password reset URL
- Sends email with reset link
- Redirects to confirmation page
- Creates new
- If email doesn't exist:
- Shows error message
- Redirects back to forgot password form
Code Implementation:
def forgotPassword(request): if request.method == "POST": email = request.POST.get('email') # verify if email exists try: user = User.objects.get(email=email) # create a new reset id new_password_reset = PasswordReset(user=user) new_password_reset.save() # creating password reset url; password_reset_url = reverse('reset-password', kwargs={'reset_id': new_password_reset.reset_id}) full_password_reset_url = f'{request.scheme}://{request.get_host()}{password_reset_url}' # email content email_body = f'Reset your password using the link below:\n\n\n{full_password_reset_url}' email_message = EmailMessage( 'Reset your password', # email subject email_body, settings.EMAIL_HOST_USER, # email sender [email] # email receiver ) email_message.fail_silently = True email_message.send() return redirect('password-reset-sent', reset_id=new_password_reset.reset_id) except User.DoesNotExist: messages.error(request, f"No user with email '{email}' found") return redirect('forgot-password') return render(request, 'forgot_password.html')Process:
- Validates that reset ID exists in database
- If valid: Shows confirmation page
- If invalid: Redirects to forgot password with error
Code Implementation:
def passwordResetSent(request, reset_id): if PasswordReset.objects.filter(reset_id=reset_id).exists(): return render(request, 'password_reset_sent.html') else: # redirect to forgot password page if code does not exist messages.error(request, 'Invalid reset id') return redirect('forgot-password')Process:
- Validates reset ID exists
- User enters new password and confirmation
- Validation checks:
- Passwords match
- Password minimum length (8 characters)
- Reset link hasn't expired (10-minute window)
- On success:
- Updates user password using
set_password() - Deletes used reset token
- Redirects to login with success message
- Updates user password using
- On error:
- Shows appropriate error messages
- For expired links: deletes token and redirects to forgot password
Code Implementation:
def resetPassword(request, reset_id): try: password_reset_id = PasswordReset.objects.get(reset_id=reset_id) if request.method == 'POST': password = request.POST.get('password') confirm_password = request.POST.get('confirm_password') passwords_have_error = False if password != confirm_password: passwords_have_error = True messages.error(request, 'Passwords do not match') if len(password) < 8: passwords_have_error = True messages.error(request, 'Password must be at least 8 characters long') # check to make sure link has not expired expiration_time = password_reset_id.created_when + timezone.timedelta(minutes=10) if timezone.now() > expiration_time: passwords_have_error = True messages.error(request, 'Reset link has expired') # delete reset id since it has expired password_reset_id.delete() # reset password if not passwords_have_error: user = password_reset_id.user user.set_password(password) user.save() # delete reset id after use password_reset_id.delete() # redirect to login messages.success(request, 'Password reset. Proceed to login') return redirect('login') else: # redirect back to password reset page and display errors return redirect('reset-password', reset_id=reset_id) except PasswordReset.DoesNotExist: # redirect to forgot password page if code does not exist messages.error(request, 'Invalid reset id') return redirect('forgot-password') return render(request, 'reset_password.html')- Unique UUID tokens: Each reset request generates a unique, non-guessable token
- Time-based expiration: Reset links expire after 10 minutes
- One-time use: Tokens are automatically deleted after successful password reset or when expired
- Email validation: Only registered email addresses can request resets
- Username uniqueness: Prevents duplicate usernames
- Email uniqueness: Prevents duplicate email addresses
- Password strength: Minimum 8-character requirement
- Password confirmation: Ensures passwords match
- Uses Django's built-in session framework
- Login required decorator protects authenticated routes
- Proper logout destroys sessions
Your templates should include the following forms:
<form method="POST"> {% csrf_token %} <input type="text" name="first_name" required> <input type="text" name="last_name" required> <input type="text" name="username" required> <input type="email" name="email" required> <input type="password" name="password" required> <input type="password" name="confirm_password" required> <button type="submit">Register</button> </form><form method="POST"> {% csrf_token %} <input type="text" name="username" required> <input type="password" name="password" required> <button type="submit">Login</button> </form><form method="POST"> {% csrf_token %} <input type="email" name="email" required> <button type="submit">Send Reset Link</button> </form><form method="POST"> {% csrf_token %} <input type="password" name="password" required> <input type="password" name="confirm_password" required> <button type="submit">Reset Password</button> </form>The system includes comprehensive error handling:
- Form validation errors are displayed using Django messages framework
- Invalid reset tokens redirect to appropriate error pages
- Expired reset links are automatically cleaned up
- User-friendly error messages for all failure scenarios
from django.contrib.auth.decorators import login_required @login_required def protected_view(request): return render(request, 'protected.html'){% if messages %} {% for message in messages %} <div class="alert alert-{{ message.tags }}">{{ message }}</div> {% endfor %} {% endif %}- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
If you encounter any issues or have questions, please open an issue on GitHub.