From Usernames to Email & Password Resets: The Evolution of User Authentication at Tutorial Haven

🔐 Feature Spotlight: User Authentication

Authentication is often the gateway to any platform—and at Tutorial Haven, we've learned that even the simplest login flow needs to adapt to real user behavior. Here's how our authentication system evolved through listening to student feedback and solving real-world problems.

The Journey: From "It Works" to "It Works for Users"

1 Phase 1: Conventional Django Authentication

Initially, I followed Django's standard pattern—username and password. It worked perfectly technically, but students soon taught me otherwise.

Issue #1: "I forgot my username"

Students struggled to recall usernames they created during registration.

Solution: Added email as an alternative login identifier

Now users could log in with either username OR email + password.

2 Phase 2: The Password Reset Challenge

Just when username/email login felt stable, another pattern emerged:

Issue #2: "I forgot my password"

A classic but critical pain point! I hadn't built a password reset feature before, but I knew it was essential for retention and user experience.

The Implementation:

  • Django's built-in authentication system came to the rescue. I discovered that Django handles the complex parts (token generation, secure validation) if you provide the right templates and views.
  • I customized the password reset templates to match our platform's branding
  • Integrated email services to send secure reset links
  • Tested thoroughly to ensure a smooth user journey from "Forgot Password?" to successful reset

🔧 Technical Highlights

Django Authentication Deep Dive

# Custom authentication backend for username OR email
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.db.models import Q

UserModel = get_user_model()

class EmailOrUsernameModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            # Check if user exists with either username or email
            user = UserModel.objects.get(
                Q(username__iexact=username) | 
                Q(email__iexact=username)
            )
        except UserModel.DoesNotExist:
            return None
        
        if user.check_password(password) and self.user_can_authenticate(user):
            return user
        return None

# In settings.py
AUTHENTICATION_BACKENDS = [
    'path.to.EmailOrUsernameModelBackend',
    'django.contrib.auth.backends.ModelBackend',
]
# Password reset URLs (built into Django!)
from django.contrib.auth import views as auth_views

urlpatterns = [
    # ... other URLs ...
    path('password-reset/', 
         auth_views.PasswordResetView.as_view(
             template_name='registration/password_reset.html'
         ),
         name='password_reset'),
    path('password-reset/done/',
         auth_views.PasswordResetDoneView.as_view(
             template_name='registration/password_reset_done.html'
         ),
         name='password_reset_done'),
    path('reset/<uidb64>/<token>/',
         auth_views.PasswordResetConfirmView.as_view(
             template_name='registration/password_reset_confirm.html'
         ),
         name='password_reset_confirm'),
    path('reset/done/',
         auth_views.PasswordResetCompleteView.as_view(
             template_name='registration/password_reset_complete.html'
         ),
         name='password_reset_complete'),
]

💡 Why This Matters

📚 Lessons Learned

🗣️ Question for You

Have you pivoted an authentication flow based on user needs? What was the biggest lesson you learned?

🔑 Key Takeaway

Authentication isn't just about security—it's about accessibility. The most secure login in the world is useless if users can't get past it. Finding the balance between security and usability is the real art of authentication design.