Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Authenticate user after password reset in Django

I use standard Django view, password_reset_confirm(), to reset user's password. After user follows password reset link in the letter, he enters new password and then view redirects him to the site root:

urls.py

url(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
        'django.contrib.auth.views.password_reset_confirm', {
        'template_name': 'website/auth/password_reset_confirm.html',
        'post_reset_redirect': '/',
    }, name='password_reset_confirm'),

After Django redirects user, he is not authenticated. I don't want him to type password again, instead, I want to authenticate him right after he set new password.

To implement this feature, I created a delegate view. It wraps standard one and handles its output. Because standard view redirects user only if password reset succeeded, I check status code of response it returns, and if it's a redirect, retrieve user from DB again and authenticate him.

urls.py

url(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
        views.password_reset_confirm_delegate, {
        'template_name': 'website/auth/password_reset_confirm.html',
        'post_reset_redirect': '/',
    }, name='password_reset_confirm'),

views.py

@sensitive_post_parameters()
@never_cache
def password_reset_confirm_delegate(request, **kwargs):
    response = password_reset_confirm(request, **kwargs)
    # TODO Other way?
    if response.status_code == 302:
        try:
            uid = urlsafe_base64_decode(kwargs['uidb64'])
            user = User.objects.get(pk=uid)
        except (TypeError, ValueError, OverflowError, User.DoesNotExist):
            pass
        else:
            user = authenticate(username=user.username, passwordless=True)
            login(request, user)
    return response

backends.py

class PasswordlessAuthBackend(ModelBackend):
    """Log in to Django without providing a password.

    """
    def authenticate(self, username, passwordless=False):
        if not passwordless:
            return None
        try:
            return User.objects.get(username=username)
        except User.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

settings.py

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'website.backends.PasswordlessAuthBackend'
)

Are there any better ways to do this?

like image 528
Marboni Avatar asked Jan 18 '15 13:01

Marboni


People also ask

How do I authenticate username and password in Django?

from django.contrib.auth import authenticate, login def my_view(request): username = request.POST['username'] password = request.POST['password'] user = authenticate(request, username=username, password=password) if user is not None: login(request, user) # Redirect to a success page. ... else: # Return an 'invalid ...

How does Django handle user authentication?

The Django authentication system handles both authentication and authorization. Briefly, authentication verifies a user is who they claim to be, and authorization determines what an authenticated user is allowed to do. Here the term authentication is used to refer to both tasks.

How do I know if a user is authenticated Django?

By calling request. user, we are able to access the user that is currently logged in. We then can use the is_authenticated property to determine if a user is currently authenticated (logged into the account). Basically, we do this all in the views.py file, which is the file where our main Python code always goes.


2 Answers

Starting Django 1.11 your can do this by using the class based view. You need to override the password_reset_confirm url to pass post_reset_login=True and success_url to PasswordResetConfirmView:

urlpatterns += [
 url(
   r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
    views.PasswordResetConfirmView.as_view(
      post_reset_login=True,
      success_url=reverse_lazy('studygroups_login_redirect')
    ),
    name='password_reset_confirm'
  ),
]
like image 132
Dirk Avatar answered Sep 21 '22 11:09

Dirk


Vanilla Django

Since password_reset_confirm is not class-based-view, you cant cleanly customize it in any significant way without resorting to middleware-type tricks. Therefore what you are doing seems to be the most efficient way at the moment.

If django would be been passing request to the SetPasswordForm (similar to how DRF passes request to serializers), you could of overwritten the form's save() to login the user there however as now that is also not possible.

3rd party packages

You can also look into other 3rd party libs which implement auth as class based views. From a quick google search, the most promising are:

  • django-password-reset. Seems to provide complete replacement for auth.views as class based views. You can overwrite form_valid in order to authenticate user.
  • django-class-based-auth-views (not complete)
like image 39
miki725 Avatar answered Sep 21 '22 11:09

miki725