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.py
from 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.py
EMAIL_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
PasswordReset
instance 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.