views:

96

answers:

2

Hi everybody; I hope you can help me figure the best way to implement a manual (server-side initiated) login without using the password. Let me explain the workflow:

  • User registers
  • Thank you! An email with an activation link has been sent blablabla
  • (Account now exists but is marked not enabled)
  • User opens email, clicks link
  • (Account is enabled)
  • Thank you! You can now use the site

What I'm trying to do is log in the user after he has clicked the email link so he can start using the website right away.

I can't use his password since it's encrypted in the DB, is the only option writing a custom authentication backend?

+5  A: 

You don't need a password to log a user in. The auth.login function just takes a User object, which you are presumably already getting from the database when you enable the account. So you can pass that straight to login.

Of course, you'll need to be very careful that there's no way a user can spoof a link to an existing already-enabled account, which would then automatically log them in as that user.

from django.contrib.auth import login

def activate_account(request, hash):
    account = get_account_from_hash(hash)
    if not account.is_active:
        account.activate()
        account.save()
        user = account.user
        login(request, user)

... etc.

Edited:

Hmm, didn't notice that requirement to use authenticate because of the extra property it adds. Looking at the code, all it does is a backend attribute equivalent to the module path of the authenticating backend. So you could just fake it - before the login call above, do this:

user.backend = 'django.contrib.auth.backends.ModelBackend'
Daniel Roseman
Thanks; the docs agree, but there's also this warning:"Calling authenticate() firstWhen you're manually logging a user in, you must call authenticate() before you call login(). authenticate() sets an attribute on the User noting which authentication backend successfully authenticated that user (see the backends documentation for details), and this information is needed later during the login process."Could this be a problem?
Agos
See my update above.
Daniel Roseman
sweet! thanks a lot
Agos
+1  A: 

Daniel's answer is very good.

Another way to do it is to create a HashModelBackend following the Custom Authorization backends http://docs.djangoproject.com/en/dev/topics/auth/#writing-an-authentication-backend like this:

class HashModelBackend(object):
    def authenticate(self, hash=None):
        user = get_user_from_hash(hash)
        return user

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

And then install this in your settings:

AUTHENTICATION_BACKENDS = (
    'myproject.backends.HashModelBackend',
    'django.contrib.auth.backends.ModelBackend',
)

Then your view would be something like this:

def activate_account(request, hash):
    user = authenticate(hash=hash)
    if user:
        # check if user is_active, and any other checks
        login(request, user)
    else:
        return user_not_found_bad_hash_message
dar