views:

505

answers:

4

My app requires users to login using their google account.

I have this set in my App.yamp file:

  • url: /user/.*
    script: user.py
    login: required

Now when any user tries to access files under /user/secret.py he will need to authenticate via google, which will redirect the user back to /user/secret.py after successful authentication. Now the problem I am facing is when the user is redirected back to the app, I cannot be sure if this is the first time the user has logged in or is it a regular user to my site who has come back again from just the user object which google passes using users.get_current_user() .

I thus need to maintain state in the datastore to check if the user already exists or not everytime. If he does not exist i need to create a new entry with other application specific settings.

My question is: Is there some easier way to handle this? without having to query the datastore to figure if this is a first time user or a regular one?

+1  A: 

Can you not just set a Cookie the first time the user logs in and check for this? If they're a new user it won't be there and but if they're an old user it will be. It's not 100% accurate since some users might clear their cookies but it might do depending on what it is you want to achieve.

If you're using Django in your application managing Cookies is pretty straightforward.

Dave Webb
Cookies are a good alternative. I wish there was something in app engine which could tell me this directly, such as maybe a property in the user class?
Jake
Cookies are a bad idea - an existing user who is using a new browser or who has cleared their cookies will show up as a new user.
Nick Johnson
+2  A: 

No, Google doesn't keep track of if a user has logged in to your app before. Since you presumably need to store some sort of state against the user, the simplest way is to try and retrieve the user's record from the datastore. If they don't have one, you can send them to the registration screen to gather this information. You can use memcache to cache a user's information and avoid extra datastore round-trips.

Nick Johnson
+1  A: 

I tend to use my own user and session manangement

For my web handlers I will attach a decorator called session and one called authorize. The session decorator will attach a session to every request, and the authorize decorator will make sure that the user is authorised

(A word of caution, the authorize decorator is specific to how I develop my applications - the username being the first parameter in most requests)

So for example a web handler may look like:

class UserProfile(webapp.RequestHandler):
  @session
  @authorize
  def get(self, user):
     # Do some funky stuff
     # The session is attached to the self object.
     someObjectAttachedToSession = self.SessionObj.SomeStuff
     self.response.out.write("hello %s" % user)

In the above code, the session decorator attaches some session stuff that I need based on the cookies that are present on the request. The authorize header will make sure that the user can only access the page if the session is the correct one.

The decorators code are below:

import functools
from model import Session
import logging

def authorize(redirectTo = "/"):
    def factory(method):
     'Ensures that when an auth cookie is presented to the request that is is valid'
     @functools.wraps(method)
     def wrapper(self, *args, **kwargs):

      #Get the session parameters
      auth_id = self.request.cookies.get('auth_id', '')
      session_id = self.request.cookies.get('session_id', '')

      #Check the db for the session
      session = Session.GetSession(session_id, auth_id)   

      if session is None:
       self.redirect(redirectTo)
       return
      else:
       if session.settings is None:
        self.redirect(redirectTo)
        return

       username = session.settings.key().name()

       if len(args) > 0:    
        if username != args[0]:
         # The user is allowed to view this page.
         self.redirect(redirectTo)
         return

      result = method(self, *args, **kwargs)

      return result
     return wrapper
    return factory

def session(method):
    'Ensures that the sessions object (if it exists) is attached to the request.'
    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):

     #Get the session parameters
     auth_id = self.request.cookies.get('auth_id', '')
     session_id = self.request.cookies.get('session_id', '')

     #Check the db for the session
     session = Session.GetSession(session_id, auth_id)   

     if session is None:
      session = Session()
      session.session_id = Session.MakeId()
      session.auth_token = Session.MakeId()
      session.put()

     # Attach the session to the method
     self.SessionObj = session   

     #Call the handler.   
     result = method(self, *args, **kwargs)

     self.response.headers.add_header('Set-Cookie', 'auth_id=%s; path=/; HttpOnly' % str(session.auth_token))
     self.response.headers.add_header('Set-Cookie', 'session_id=%s; path=/; HttpOnly' % str(session.session_id))

     return result
    return wrapper

def redirect(method, redirect = "/user/"):
    'When a known user is logged in redirect them to their home page'
    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
     try: 
      if self.SessionObj is not None:
       if self.SessionObj.settings is not None:
        # Check that the session is correct
        username = self.SessionObj.settings.key().name()

        self.redirect(redirect + username)
        return
     except:
      pass
     return method(self, *args, **kwargs)
    return wrapper
Kinlan
A: 

I agree that managing your own authenticated users is the best way to approach this problem. Depending on your application scope obviously but at the very least an AuthUser(Model) class that contains the UserProperty for the users that have logged in with your account.

...
class AuthUser(db.Model):
  user = UserProperty(required=True)
...

Then when a user logs in just

...
user = users.get_current_user()
user_exists = AuthUser.gql('where user = :1', user) # or easy check db.GqlQuery("select __key__ from AuthUser where user = :1", user)
if user_exists:
  # do user has been before stuff
else:
  # do first time user stuff
...

Alternately a super easy way to do this is have a Model for your site that has a ListProperty(users.User) and then you can easily check the list to see if the user has been into your app before.

...
class SiteStuff(db.Model):
  auth_users = ListProperty(users.User)
...

and when they log in: check if they are in the list; if not, you add them to the list, put() it and do whatever you need to do for first time users. If you find them in there then do the other stuff.

Gabriel