Turbogears, remember me

So a while back I implemented a remember me feature for chrss. I said I'd release the code for it and am finally now getting round to it.

Please note that this kind of "remember me" functionality can represent a potentially security hole. It makes sense for some sites where the convenience out weighs any problems that would occur if someone fraudulently gains access to the site. As I wrote this for a site that is concerned with playing chess online it seemed worth it.

So to get started this is meant to work with:

Also note that I've left some of the imports as they appear for my app (chrss), so you'll need to change them as appropriate.

The idea

Conceptually a regular request with a remember me feature works thus:

  • If the user is not logged in, we check for a "remember me" cookie
  • If the cookie is present then we check to see if it matches a token (which maps to a user) in the database
  • If there's a match to a user we can login the user and on future requests we can ignore the remember me cookie (everything works as before)

The token in the database is randomly generated when the user logs in (with the "remember me" option ticked on the login form) in a similar way to any kind of session tracking cookie. The different is that the token/cookie is meant to hang around for much longer than a regular session. It's used in addition to Turbogears tg-visit cookie and is just a handy shortcut for logging in a user automatically. This means that it's fairly non-invasive in so far as it interacts with the Turbogears identity framework.

The code

First of all we need a table in the database to connect the remember me token to a user. So in my models I defined the following entity:

class RememberMe(SQLObject):
    user_token = StringCol(length=40, alternateID=True,
    user_id = IntCol()
    expiry = DateTimeCol()


The rest of the code then lives in remember_me.py.

First there's the code to "remember" a user. This creates a RememberMe entity and sets a cookie on the user's machine:

def generate_token():
    key_string= '%s%s%s%s' % (random.random(), datetime.now(),
    return sha.new(key_string).hexdigest()

def remember_user(user):
    from chrss.model import RememberMe

    expiry=datetime.now() + timedelta(days=remember_me_age_days)
    remember=RememberMe(user_token=user_token, user_id=user.id,expiry=expiry)

    cookies= cherrypy.response.simple_cookie
    max_age = remember_me_age_days*24*60*60
    cookies[remember_cookie_name] = remember.user_token
    cookies[remember_cookie_name]['path'] = '/'
    cookies[remember_cookie_name]['expires'] = formatdate(time() + max_age)
    cookies[remember_cookie_name]['max-age'] = max_age

Here's the reverse function to "un-remember" a user (which you would call from your logout method):

def unremember_user(user):
    cookies = cherrypy.request.simple_cookie
    if remember_cookie_name in cookies:

        if user_token:
            from chrss.model import RememberMe
            except SQLObjectNotFound:

            # now clear cookie
            cookies= cherrypy.response.simple_cookie
            cookies[remember_cookie_name] = ''
            cookies[remember_cookie_name]['path'] = '/'
            cookies[remember_cookie_name]['expires'] = 0
            cookies[remember_cookie_name]['max-age'] = 0

Before I get onto the two monkey patches, we need to make one more function, that we use to login the user given a user entity (bypassing the need for their username and password) and is based on code from here:

def login_user(user):
    ''' from http://docs.turbogears.org/1.0/IdentityRecipes'''
    visit_key = turbogears.visit.current().key
    IdentityObject = turbogears.identity.soprovider.SqlObjectIdentity

    from chrss.model import VisitIdentity
        link = VisitIdentity.by_visit_key(visit_key)
    except SQLObjectNotFound:
        link = None
    if not link:
        link = VisitIdentity(visit_key=visit_key, user_id=user.id)
        link.user_id = user.id
    user_identity = IdentityObject(visit_key);
    return user_identity

The monkey patches

Now we get to the meat of the code - the bit which does the actual "magic". In both cases we are monkey-patching methods that belong to the IdentityVisitPlugin class in Turbogears (defined in turbogears.identity.visitor).

First up is identity_from_visit which normally just looks for the tg-visit cookie and then sees if that's associated with a user login or not. We shall effectively override it, so that if no association is found then we will perform a further check to see if there is a remember me cookie that will let us log the user in:

# keep a reference to the original function

def identity_from_remember_me( self, visit_key ):
    identity=old_identity_from_visit( self, visit_key )
    if identity.anonymous:
        # not logged in so check for remember me cookie
        cookies = cherrypy.request.simple_cookie
        if remember_cookie_name in cookies:
            log.info("checking remember me cookie")

            from chrss.model import RememberMe, User
                return login_user(user)
            except SQLObjectNotFound:

    return identity

# monkey-patch the method

The next method we patch is identity_from_form. For this we just check whether there is a "remember_me" parameter in the request after a successful login (from calling the original method) and if so call the remember_user() function.


def identity_from_form(self, visit_key):
    identity=old_identity_from_form(self, visit_key)
    if identity is not None and not identity.anonymous:
        # login worked, so now see if 'remember me' set
        remember_me=params.pop('remember_me', None)
        if remember_me:
    return identity


You'll just import the remember_me module early on in starting up your Turbogears app and it will apply these monkey patches. Then if you modify your login template to include a "remember_me" checkbox you should have everything working.

As I said before it's fairly non-invasive (as far as monkey patches go), so there shouldn't really be a need to modify much beyond your login form and to add a call to unremember_user to your logout code. The only other thing is perhaps to setup a cron-script or other background task to delete expired entries in the database (which is why the RememberMe entity has an expiry column).

Source code

The remember_me module is available for download here.