Managing users authentication in Google App Engine
Question: 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?


Answer: I understand the confusion, but the system is "working as designed".

At any point in time a GAE handler can have zero or one "logged-in user" (the
object returned by `users.get_current_user()`, or `None` if no logged-in user)
**and** zero or more "oauth2 authorization tokens" (for whatever users and
scopes have been granted and not revoked).

There is no constraint that forces the oauth2 thingies to match, in any sense,
the "logged-in user, if any".

I would recommend checking out the very simple sample at
<https://code.google.com/p/google-api-python-
client/source/browse/samples/appengine/main.py> (to run it, you'll have to
clone the whole "google-api-python-client" package, then copy into the
`google-api-python-client/source/browse/samples/appengine` directory
directories `apiclient/` and `oauth2client/` from this same package as well as
`httplib2` from <https://github.com/jcgregorio/httplib2> \-- and also
customize the `client_secrets.json` \-- however, you don't need to run it,
just to read and follow the code).

This sample doesn't even **use** `users.get_current_user()` \-- it doesn't
need it nor care about it: it only shows how to use `oauth2`, and there is
_no_ connection between holding an `oauth2`-authorized token, and the `users`
service. (This allows you for example to have cron execute on behalf of one or
more users certain tasks later -- cron doesn't log in, but it doesn't matter
-- if the oauth2 tokens are properly stored and retrieved then it can use
them).

So the code makes a decorator from the client secrets, with
`scope='https://www.googleapis.com/auth/plus.me'`, then uses
`@decorator.oauth_required` on a handler's `get` to ensure authorization, and
with the decorator's authorized `http`, it fetches

    
    
    user = service.people().get(userId='me').execute(http=http)
    

with `service` built earlier as `discovery.build("plus", "v1", http=http)`
(with a different non-authorized `http`).

Should you run this locally, it's easy to add a fake login (remember, user
login is faked with dev_appserver) so that `users.get_current_user()` returns
`princess@bride.com` or whatever other fake email you input at the fake login
screen -- and this in no way inhibits the completely separate `oauth2` flow
from still performing as intended (i.e, exactly the same way as it does
without any such fake login).

If you deploy the modified app (with an extra user login) to production, the
login will have to be a real one -- but it's just as indifferent to, and
separate from, the `oauth2` part of the app.

If your application's logic does require constraining the `oauth2` token to
the specific user who's also logged into your app, you'll have to implement
this yourself -- e.g by setting `scope` to
`'https://www.googleapis.com/auth/plus.login
https://www.googleapis.com/auth/plus.profile.emails.read'` (plus whatever else
you need), you'll get from `service.people().get(userId='me')` a `user` object
with (among many other things) an `emails` attribute in which you can check
that the authorization token is for the user with the email you intended to
authorize (and take remedial action otherwise, e.g via a logout URL &c).
((This can be done more simply and in any case I doubt you really need such
functionality, but, just wanted to mention it)).

