1

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.

blueFast
  • 41,341
  • 63
  • 198
  • 344
  • "I would like to check whether the access token has expired when creating the session, not when requesting data." This is not possible. You should instead use the expiry that's returned when authorizing a user. This implies using `get_raw_access_token` and examining the full response. – maxcountryman Apr 20 '13 at 13:42
  • That is strange (I believe you!). But I am sending the `access_token` when creating the session. I assume the library internally performs some requests to the service provider (google in this case). And the service provider knows that the token has expired. Why is not telling so when replying to the session creation request? Is this a service provider issue or a library issue? Maybe I misunderstand what creating a session means, and there is no actual communication happening with the service provider at this stage. – blueFast Apr 22 '13 at 12:21
  • There is no network communication happening when you call `get_session` and supply an access token; that just creates a new session object with that access token. If you were using `get_auth_session`, there are network calls made, but there is no standard formatting for expiry errors and thus it cannot be handled automatically by the library. – maxcountryman Apr 22 '13 at 14:04

1 Answers1

5

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

Community
  • 1
  • 1
gatto
  • 2,947
  • 1
  • 21
  • 24
  • You're right that `get_auth_session` can't be used for this (and excellent explanation btw); I've considered changing this mechanism slightly so the data from the response is retained on the session so as to permit this but it needs more thought I think. – maxcountryman Apr 20 '13 at 14:58
  • What about checking for callback in kwargs? If present, call it with the data. On one hand it won't mess with internals at all, on the other hand it's not very convenient for users. I think, I'd do it your way, just put original response into session. – gatto Apr 20 '13 at 15:27
  • I think then the problem is the library assumes you're using some kind of server, which isn't necessarily a requirement rauth imposes on its users: you can use rauth outside of the context of a web app, for example. – maxcountryman Apr 20 '13 at 15:30
  • By callback I meant just a python function, which receives parsed response as argument, no need for server or anything. Anyway, callback isn't a great idea. Google's oauth2 lib stores refresh_token as well as expire_in time in its session object, and does refresh automatically if needed and if possible. – gatto Apr 20 '13 at 15:48
  • Is there an example of `refresh_token` somewhere? I can not find it in the API (http://rauth.readthedocs.org/en/latest/api/), I can not find it in the code ... – blueFast Apr 23 '13 at 07:31
  • I added example of how to use `refresh_token`. – gatto Apr 23 '13 at 07:57
  • Thanks, but in my case, in the reply to `get_raw_access_token` there is no `refresh_token`. You can see a full `Flask` implentation here: https://github.com/gonvaled/rauth/blob/master/examples/google/google.py – blueFast Apr 23 '13 at 12:53
  • I just checked, it is there in json response, but only if it's the first time user authorises your application. You either have to revoke access or force for re-prompt. See the answer edit. – gatto Apr 23 '13 at 13:29
  • That looks great! I will implement that right away and push a new version. I hope @maxcountryman can accept the pull request. – blueFast Apr 23 '13 at 13:38
  • @gonvaled Please do send a pull request. We'll probably do another release this week. – maxcountryman Apr 23 '13 at 14:20