views:

91

answers:

3

In my web app I would like to be able to email self-authenticating links to users. These links will contain a unique token (uuid). When they click the link the token being present in the query string will be enough to authenticate them and they won't have to enter their username and password.

What's the best way to do this?

+1  A: 

From a security standpoint, this seems like a really bad idea.

That said, it can still be done. I'm hoping you're planning on using this only on something that would be internal or a company intranet of some sort. For a live, on-the-web, legit website, this is probably just asking for trouble.

You can handle incoming requests by creating a middleware component to do so.

(untested, but the general idea)

import base64

class UUIDQueryStringMiddleware(object):

    def process_request(request):

        if request.method == 'GET':
            if not request.user.is_authenticated():
                uuid = request.REQUEST.get('u', None)
                if uuid:
                    username = base64.b64decode(uuid)
                    try:
                        user = User.objects.get(username=username)
                        request.user = user
                    except:
                        pass

        # Pass the original request back to Django
        return request

You would then need to setup this middleware to run before the auth and sessions middleware runs...

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'yourapp.middleware.UUIDQueryStringMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
)

See this question for more details on encoding/decoding: encrypt & decrypt strings in python

I REALLY hope you're not going to use this on a live site.

T. Stone
What about if I add these restrictions:1) Only a single one of my @login_required views may be authenticated in this fashion. This view does not allow read or write access to any account information.2) Once the token is used for a single login, it is invalidated.Do you feel these steps are adequate? Any idea how I could pull this off? I imagine its pretty similar to your middleware logic, except it runs in a custom authentication backend. But how could I handle the user refreshing the page?
awolf
@awolf: I think you're on the right track. What you might want to consider is making the token a hash of the user information and the location where they are authenticated. This way you can use the answer from @Yaroslav, just without using `auth.login()` which will authenticate the user for the entire session.
Jack M.
+1  A: 

To T. Stone's point, end users are notorious for passing links around -- ESPECIALLY those in an intraweb environment. Having something that authenticated the user automatically means that I can almost guarantee that you're going to have at least one person logged in as somebody else.

There are better ways, of course, to refresh a user's session from Cookie information, so you at least have a decent idea that they're a valid user attached to a valid browser and can feel SOMEwhat safer in that they aren't likely to hand their laptops about like they might with a link, but yes... to reiterate, what you're trying to do is a VERY bad idea if your app has more than 1 user.

bmelton
Not only passing it around, but it also stays in most web browser's history for later access by someone else, and it also gets logged on proxies and all kinds of stuff.
T. Stone
I would like the tokens to be one-time use.
awolf
+3  A: 

That's quite common task if you properly expire your links :) You'll need to implement your own authentication backend. Instead of checking username and password parameters you'll check for auth_link.

class AuthLinkBackend(object):
  def authenticate(auth_link = None):
    if auth_link:
      # validate and expire this link, return authenticated user if successful
      return user

Add your backend class to the list of backends (AUTHENTICATION_BACKENDS setting).

In the link validation view you should try to authenticate user and if it succeeded, log in him/her:

user = auth.authenticate(auth_link=link)
if user:
  auth.login(request, user)
Yaroslav
Once you call `auth.login(request, user)`, this use is now authenticated in the session, correct? Meaning that if Suzy sends Tommy a link authenticated as Suzy, Tommy has access to everything that Suzy does? This seems a quite a bit insecure?
Jack M.
@Jack, original poster mentioned that he is going to invalidate auth link once it is used.
Yaroslav
BTW, it's common practice with 'reset password' feature when you click the link, enter new password and become logged in immediately.
Yaroslav