This post is a followup to How to do OAuth-requiring operations in a GAE cron job?, where I realized I'm mis-using the @oauth_required
decorator from OAuth2DecoratorFromClientSecrets
.
As described by the OAuth 2.0 explained presentation, Oauth 2.0 solves the problem of:
- Building a service...
- ... accessed by a user...
- ... and accessing the user's data from a third party.
That's what @oauth_required
abstracts, and it does it well (currently my app "works": if I trigger the refresh page, I'm being asked to authorize access to my youtube data to my app, and the rest follows). But that's not what I want! My app does something simpler, which is creating a youtube playlist every day with my credentials and without any user input. So to compare to the above 3-tier negociation, I want:
- A service
- ... accessed by users
- ... but only accessing "server-owned" YouTube playlist data. I don't want any access to the user's YouTube data, I just want to modify a playlist I (i.e. me / a userid persisted by the server) own.
But I still need help to do that; here is my current state:
After a few searches I learned that what I want to do is called Offline Access (emphasis mine, which is almost exactly my use case):
"In some cases, your application may need to access a Google API when the user is not present. Examples of this include backup services and applications that make blogger posts exactly at 8am on Monday morning. This style of access is called offline, and web server applications may request offline access from a user. The normal and default style of access is called online."...
→ So I should keep doing what I'm doing right now, keep requesting access to my YouTube account, but do it using thetype_access=offline
flag to get a token, and persist/use it for subsequent requests.The Offline Access and Using a Refresh Token sections make total sense, but stay at a general HTTP level. Being still a newbie, I don't see how to integrate those principles into my Python code, and I didn't find any sample Python code around....
→ Could anyone help me with one Python example illustrating how and where to use this flag?... and in particular, after studying
oauth2client.appengine.OAuth2Decorator.oauth_required
, I'm still not sure if I can bend it to my case, or if I should do my own thing.
→ What do you think?
Thanks for your time; if needed I'm also hanging out on irc://irc.freenode.net/#appengine as ronj
.
Offline access is the default when retrieving tokens; you may have noticed this in the OAuth dialog that comes up:
When your user accepts the OAuth dialog in a method decorated with
decorator.oauth_required
the credentials for that user will be stored in the datastore, including the refresh token.Once you have one of these credentials objects, you can use it so authorize an HTTP object for calling APIS:
and once authorized, it will do all the work for you. So if the
access_token
is expired, the first API response will be a401
and so thecredentials
object will use therefresh_token
to get a newaccess_token
and make the request again.If you know the user ID, you can retrieve the
credentials
from the datastore as described in How to do OAuth-requiring operations in a GAE Task Queue?:Note/Gotcha:
If a user has already authorized your client ID, the subsequent times you perform OAuth for these users they will not see the OAuth dialog and you won't be given a refresh token. A refresh token can only be given if they go through the OAuth dialog, but since the user had already authorized your client ID, the spec assumes you would already have a refresh token around.
This often comes up when developers are testing OAuth, since they will go through the flow multiple times with a test account and after accepting the 2nd, 3rd, 4th, ... times, they never see the refresh token. A simple way around this is to use
approval_prompt=force
as an argument to theOAuth2Decorator
constructor. This will force the OAuth dialog to appear every time you perform OAuth for a user.However, this will not cause the dialog to show up every time a request is served for a given user; this would be a TERRIBLE user experience. Instead, the
SACSID
cookie from the request can be used (by the client library and some App Engine libraries) to determine who the current user is. Once the the library knows that current user, it can get your existing stored token/credentials
for that user from the datastore and no jarring dialog will be needed.