How to log a user in automatically after a password reset?

45 views
Skip to first unread message

JC Briar

unread,
Nov 27, 2012, 3:07:03 PM11/27/12
to django...@googlegroups.com
When a user has forgotten their password, I use django.contrib.auth.views.password_reset to send an email containing a temporary link. When the user follows that link, I use password_reset_confirm to let them set a new password. So far, so good.

But password_reset_confirm doesn't automatically log the user in – as a result, immediately after resetting their password the user has to enter it again in order to log in. Most users, I suspect, would find this to be obnoxious.

How could this be fixed? According to the auth documentation, I need to call authenticate() and login(). But where? I could supply my own SetPasswordForm subclass to password_reset_confirm, and make the calls in its save() method... except save() doesn't have access to the HttpRequest object that login() requires. Or I could define my own password_reset_complete view... but then I wouldn't have access to the username and password anymore. Are there other options?

Russell Keith-Magee

unread,
Nov 27, 2012, 7:45:58 PM11/27/12
to django...@googlegroups.com
What you need to do is write your own password_reset_confirm() method.

You could tackle this three ways, depending on how much custom code you want to write.

Firstly, reinvent the wheel. Copy and paste the original, and insert your code to log in the user on success. Simple, and it works, but it's ugly.

Secondly, don't reinvent the wheel :-) The wonderful thing about Django is that views are just methods - which means you can invoke them:

def my_password_reset_confirm(request):
    response = password_reset_confirm(request)
    do_stuff()
    return response

So - write a custom password_reset_confirm method that does all the usual reset confirmation, and integrates the calls to authenticate and login(). If the original method returns a HTTPRedirect, you know the reset was successful, so you can reprocess the POST data to extract the username and password.

Thirdly, don't reinvent the wheel, and don't reprocess form data either -- exploit duck typing. The existing password_reset_confirm takes the form as an argument; the only problem is that the form doesn't have access to the request. password_reset_confirm takes the form class as an argument, but through the wonders of duck typing, there's nothing that says you need to provide a form class -- you just need to provide something that takes the same arguments as a form class constructor. 

So, subclass the form so that it *does* take the request as an argument, and put the login code as part of the form save() method. Then, write a method that has the same prototype as the constructor for the form that the password_reset_confirm() method is expecting, but returns an instance of *your* form, bound with the current request. Abracadabra - you've got the request object provided to the password reset form!

Yours,
Russ Magee %-)
 

JC Briar

unread,
Nov 28, 2012, 11:32:51 AM11/28/12
to django...@googlegroups.com
Option 1 doesn't hold much appeal, for all the reasons you mention.

Option 2 is a possibility, I suppose. Checking to see if password_reset_confirm returns an HttpRedirect is a possibility I hadn't considered.

Option 3 is intriguing. But this part has me scratching my head:

 
Then, write a method that has the same prototype as the constructor for the form that the password_reset_confirm() method is expecting, but returns an instance of *your* form, bound with the current request.
 
Okay, how could this method create a form bound with the current request? How is the method getting its mitts on the current request? The method is being passed to password_reset_confirm via the URL dispatcher – that is, I would need to have code like the following in my urls.py:

  url(r'^password/set/(?P<uidb36>\w+)/(?P<token>[-\w]+)',
    'password_reset_confirm', {
      'set_password_form': abracadabra_method,
    }),

Where does abracadabra_method get the current request?

Russell Keith-Magee

unread,
Nov 28, 2012, 8:36:05 PM11/28/12
to django...@googlegroups.com
Sorry - I probably wasn't clear - option 3 is an extension of option 2. You exploit the fact that you can call a view, and provide a generated function as the argument:

def bind_form(request):
    def create_form_instance(data):
        return MyForm(request, data)
   return create_form_instance

def my_password_reset_confirm(request):
    return password_reset_confirm(request, set_password_form=bind_form(request))

Yours,
Russ Magee %-)

JC Briar

unread,
Nov 28, 2012, 9:44:29 PM11/28/12
to django...@googlegroups.com
Ah, that's pretty slick! Thanks for the explanation.
Reply all
Reply to author
Forward
0 new messages