I am working on a webapp based on google app engine. The application uses the google authentication apis. Basically every handler extends from this BaseHandler and as first operation of any get/post the checkAuth is executed.
class BaseHandler(webapp2.RequestHandler):
googleUser = None
userId = None
def checkAuth(self):
user = users.get_current_user()
self.googleUser = user;
if user:
self.userId = user.user_id()
userKey=ndb.Key(PROJECTNAME, 'rootParent', 'Utente', self.userId)
dbuser = MyUser.query(MyUser.key==userKey).get(keys_only=True)
if dbuser:
pass
else:
self.redirect('/')
else:
self.redirect('/')
The idea is that it redirects to / if no user is logged in via Google OR if there is not a User in my db of users having that google id.
The problem is that I can succesfully log in my web app and make operations. Then, from gmail, o Logout from any google account BUT if i try to keep using the web app it works. This means the users.get_current_user() still returns a valid user (valid but actually OLD). Is that possible?
IMPORTANT UPDATE I Do Understand what explained in the Alex Martelli's Comment: There is a cookie which keeps the former GAE authentication valid. The problem is that the same web app also exploits the Google Api Client Library for Python https://developers.google.com/api-client-library/python/ to perform operations on Drive and Calendar. In GAE apps such library can be easily used through decorators implementing the whole OAuth2 Flow (https://developers.google.com/api-client-library/python/guide/google_app_engine).
I therefore have my Handlers get/post methods decorated with oauth_required like this
class SomeHandler(BaseHandler):
@DECORATOR.oauth_required
def get(self):
super(SomeHandler,self).checkAuth()
uid = self.googleUser.user_id()
http = DECORATOR.http()
service = build('calendar', 'v3')
calendar_list = service.calendarList().list(pageToken=page_token).execute(http=http)
Where decorator is
from oauth2client.appengine import OAuth2Decorator
DECORATOR = OAuth2Decorator(
client_id='XXXXXX.apps.googleusercontent.com',
client_secret='YYYYYYY',
scope='https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file'
)
It usually works fine. However (!!) when the app is idle for a long time it happens that the oauth2 decorator redirects me to the Google authentication page where, if I change account (I have 2 different accounts) Something WEIRD happens: The app is still logged as the former account (retrieved through users.get_current_user()) while the api client library, and thus the oauth2 decorator, returns data (drive, calendar, etc.) belonging to the second account.
Which is REALLY not appropriate.
Following the example above (SomeHandler class) suppose I am logged as Account A. The users.get_current_user() always returns A as expected. Now suppose I stopped using the app, after a long while the oauth_required redirects me to the Google Account page. I therefore decide (or make a mistake) to log is as Account B. When accessing the Get method of the SomeHandler class the userId (retrived through users.get_current_user() is A while the list of calendars returned through the service object (Google Api client Library) is the list of calendars belonging to B (the actual currently logged user).
Am I doing something wrong? is Something expected?
Another Update
this is after the Martelli's Answer. I have updated the handlers like this:
class SomeHandler(BaseHandler):
@DECORATOR.oauth_aware
def get(self):
if DECORATOR.has_credentials():
super(SomeHandler,self).checkAuth()
uid = self.googleUser.user_id()
try:
http = DECORATOR.http()
service = build('calendar', 'v3')
calendar_list = service.calendarList().list(pageToken=page_token).execute(http=http)
except (AccessTokenRefreshError, appengine.InvalidXsrfTokenError):
self.redirect(users.create_logout_url(
DECORATOR.authorize_url()))
else:
self.redirect(users.create_logout_url(
DECORATOR.authorize_url()))
so basically I now use oauth_aware and, in case of none credentials I logout the user and redirect it to the DECORATOR.authorize_url()
I have noticed that after a period of inactivity, the handler raises AccessTokenRefreshError and appengine.InvalidXsrfTokenError exceptions (but the has_credentials() method returns True). I catch them and (again) redirect the flow to the logout and authorize_url()
It seems to work and seems to be robust to accounts switch. Is it a reasonable solution or am I not considering some aspects of the issue?