How to detect expired access tokens when creating the session?

blueFast picture blueFast · Apr 20, 2013 · Viewed 9.9k times · Source

I have the following test program:

from   rauth.service import OAuth1Service, OAuth2Service

SUPPORTED_SERVICES = {
    'twitter'  : ( 'OAuth1', 'twitter',  'https://api.twitter.com/oauth',        'request_token', 'access_token', 'authorize', 'https://api.twitter.com/1/',  None),
    'facebook' : ( 'OAuth2', 'facebook', 'https://graph.facebook.com/oauth',     None,            'access_token', 'authorize', 'https://graph.facebook.com/', 'https://www.facebook.com/connect/login_success.html'),
    'google'   : ( 'OAuth2', 'google',   'https://accounts.google.com/o/oauth2', None,            'token',        'auth',      None,                          'http://localhost'),
}

CLIENT_DATA = {
    'twitter'  : ('dummy_client_id', 'dummy_client_secret'),
    'facebook' : ('dummy_client_id', 'dummy_client_secret'),
    'google'   : ('dummy_client_id', 'dummy_client_secret'),
}

USER_TOKENS = {
    'user1' : {
        'twitter'  : ('dummy_access_token', 'dummy_access_token_secret'),
        'facebook' : ('dummy_access_token', None),
        'google'   : ('dummy_access_token', None),
    }
}

def test_google(user_id):
    service_id = 'google'
    oauthver, name, oauth_base_url, request_token_url, access_token_url, authorize_url, base_url, redirect_uri = SUPPORTED_SERVICES[service_id]
    request_token_url = oauth_base_url + '/' + (request_token_url or '')
    access_token_url  = oauth_base_url + '/' + access_token_url
    authorize_url     = oauth_base_url + '/' + authorize_url
    client_id, client_secret = CLIENT_DATA[service_id]
    google = OAuth2Service(
        client_id=client_id,
        client_secret=client_secret,
        name=name,
        authorize_url=authorize_url,
        access_token_url=access_token_url,
        base_url=base_url)
    access_token, access_token_secret = USER_TOKENS[user_id][service_id] # access_token_secret only needed for twitter (OAuth1)
    session = google.get_session(access_token)
    user = session.get('https://www.googleapis.com/oauth2/v1/userinfo').json()
    print user

test_google('user1')

I have authorized my application to access the google account of user1, and obtained an access_token. That access token has already expired, and the output of my program is:

{u'error': {u'code': 401, u'message': u'Invalid Credentials', u'errors': [{u'locationType': u'header', u'domain': u'global', u'message': u'Invalid Credentials', u'reason': u'authError', u'location': u'Authorization'}]}}

I would like to check whether the access token has expired when creating the session, not when requesting data. Is this possible? How can I verify if a session object is really authorized?

For clarification, what I am trying to do is the following:

  1. First, let the user authorize my application
  2. Save the access token for future use (in a database, but in the test code this is hardcoded in the script)
  3. Whenever a later access detects that the token has expired, go back to step 1

I am currently having trouble with step 3. I can of course detect the 401 in the json reply to my GET, but it looks rather cumbersome to be forced to verify all GET accesses. What I am trying to do is to verify that the session is really active when I create it, and then assume for the whole duration of the session object, that it will stay active. Usually this will be just several milliseconds, while my webapp is processing the request and accessing the google API using the OAuth session object.

Answer

gatto picture gatto · Apr 20, 2013

You have to call get_authorization_url first, which user must open and grant you permissions to access his account, in return you will get a code from redirect_uri callback's query params, which you can exchange for access_token:

params = {
    'scope': 'email',
    'response_type': 'code',
    'redirect_uri': redirect_uri,
    'access_type': 'offline', # to get refresh_token
}

print google.get_authorize_url(**params)

According to documentation this code should work:

data = {
    'code': 'code you got from callback',
    'grant_type': 'authorization_code',
    'redirect_uri': 'http://localhost/oauth2',
}

response = google.get_raw_access_token(data=data)

In response you will get a JSON data like this:

{
  "access_token" : "ya29.AHE<....>n3w",
  "token_type" : "Bearer",
  "expires_in" : 3600,
  "id_token" : "eyJh<...>QwNRzc",
  "refresh_token" : "1/X86S<...>Vg4"
}

As you can see there is expires_in (seconds), you have to store the time when you got the token and compare at later with current time + expires_in.

If the token expired, you can refresh it with refresh_token later without asking for user confirmation again:

response = google.get_raw_access_token(data={
    'refresh_token': refresh_token,
    'grant_type': 'refresh_token',
})
print response.content

Notice, that refresh_token will only be returned the first time user authorises the app. See this question for details.

Alas it seems that you can't use get_auth_session, because internally it only extracts access_token and everything else is discarded.

If you get access_token immediately without getting auth code first, you still get expires_in in callback. From the docs:

https://oauth2-login-demo.appspot.com/oauthcallback#access_token=1/fFBGRNJru1FQd44AzqT3Zg&token_type=Bearer&expires_in=3600