Django IA: Auth Password Reset

Django comes with a lot of great built-in functionality. One of the most useful contrib apps is authentication, which (among other things) provides views for login, logout, and password reset. Login & logout are self-explanatory, but resetting a password is, by nature, somewhat complicated. Because it’s a really bad idea to store passwords as plaintext, you can’t just send a user their password when they forget it. Instead, you have to provide a secure mechanism for users to change their password themselves, even if they can’t remember their original password. Lucky for us, Django auth provides this functionality out of the box. All you need to do is create the templates and hook-up the views. The code you need to write to make this happen is pretty simple, but it can be a bit tricky to understand how it all works together. There’s actually 4 separate view functions that together provide a complete password reset mechanism. These view functions are

  1. password_reset
  2. password_reset_done
  3. password_reset_confirm
  4. password_reset_complete

Here’s an Information Architecture diagram showing how these views fit together, using Jesse James Garrett’s Visual Vocabulary. The 2 black dots are starting points, and the circled black dot is an end point.

Django Auth Password Reset IA

Here’s a more in-depth walk-thru of what’s going on, with a fictional user named Bob:

  1. Bob tries to login and fails, probably a couple times. Bob clicks a “Forgot your password?” link, which takes him to the password_reset view.
  2. Bob enters his email address, which is then used to find his User account.
  3. If Bob’s User account is found, a password reset email is sent, and Bob is redirected to the password_reset_done view, which should tell him to check his email.
  4. Bob leaves the site to check his email. He finds the password reset email, and clicks the password reset link.
  5. Bob is taken to the password_reset_confirm view, which first validates that he can reset his password (this is handled with a hashed link token). If the token is valid, Bob is allowed to enter a new password. Once a new password is submitted, Bob is redirected to the password_reset_complete view.
  6. Bob can now login to your site with his new password.

This final step is the one minor issue I have with Django’s auth password reset. The user just changed their password, why do they have to enter it again to login? Why can’t we eliminate step 6 altogether, and automatically log the user in after they reset their password? In fact, you can eliminate step 6 with a bit of hacking on your own authentication backend, but that’s a topic for another post.

  • http://crucial-systems.com/ felix

    you don’t need to write your own backend to change the reset/login behavior. you just have to use the URL config to direct to your own view

    url(r’^reset/(?P[0-9A-Za-z]+)-(?P.+)/$’,
    ‘accountz.views.password_reset_confirm’,
    name=’password_reset_confirm’),

    and then use this to log them in:

    from django.contrib import auth

    def login_user(user,request):
    “”” not a view, a helper “””

    # whatever my current backend is
    bs = auth.get_backends()
    backend = bs[0] # the first one, usually the EmailBackend
    user.backend = “%s.%s” % (backend.__module__, backend.__class__.__name__)

    auth.login(request,user)

  • http://crucial-systems.com felix

    you don’t need to write your own backend to change the reset/login behavior. you just have to use the URL config to direct to your own view

    url(r’^reset/(?P[0-9A-Za-z]+)-(?P.+)/$’,
    ‘accountz.views.password_reset_confirm’,
    name=’password_reset_confirm’),

    and then use this to log them in:

    from django.contrib import auth

    def login_user(user,request):
    “”” not a view, a helper “””

    # whatever my current backend is
    bs = auth.get_backends()
    backend = bs[0] # the first one, usually the EmailBackend
    user.backend = “%s.%s” % (backend.__module__, backend.__class__.__name__)

    auth.login(request,user)

  • http://crucial-systems.com/ felix

    also this is a good idea after logging the account in:

    account.last_login = datetime.datetime.now()
    account.save()
    account.message_set.create(message=’Your password has been set. Welcome to %s’ % settings.SITE_NAME)

  • http://crucial-systems.com felix

    also this is a good idea after logging the account in:

    account.last_login = datetime.datetime.now()
    account.save()
    account.message_set.create(message=’Your password has been set. Welcome to %s’ % settings.SITE_NAME)

  • http://streamhacker.com/ Jacob Perkins

    Thanks Felix, I do something very similar to do an automatic login after password reset. But I forgot to set the last_login and user message; I’ll have to add that to my code :)

  • Jacob

    Thanks Felix, I do something very similar to do an automatic login after password reset. But I forgot to set the last_login and user message; I’ll have to add that to my code :)

  • S Kujur

    All is fine and dandy. But by default, the email is not required for a user to create his/her account, is that not so?

  • http://streamhacker.com/ Jacob Perkins

    Yes, the email field is optional by default, but I usually create custom form to make it required. Otherwise the whole password reset thing won't work.

  • S Kujur

    All is fine and dandy. But by default, the email is not required for a user to create his/her account, is that not so?

  • http://streamhacker.com/ Jacob Perkins

    Yes, the email field is optional by default, but I usually create custom form to make it required. Otherwise the whole password reset thing won't work.