oauth2client でリフレッシュトークンが取得できない

2017年4月17日 21:30

Google Spreadsheet などのサービスの API とやりとりする Google App Engine(GAE)アプリを比較的多く手がけているが、その際に利用するのが OAuth2 という認証プロトコルである。スマホアプリなどで「このアプリケーションが次の機能へのアクセスを求めています」云々というページが表示されることがあるが、あれこそがこの OAuth2(バージョン1かもしれないが)による認証フローである。ひとまずここでは OAuth2 自体の仕組みについては説明しない。

GAE で OAuth2 を利用するには、oauth2client というライブラリを使うのが一般的である。パッケージ内に appengine.py というそのものずばりのファイルがあり、ツイートに書いた OAuth2DecoratorFromClientSecrets というクラスもここに書かれている。そして、以下のようなサンプルも記されている。

decorator = OAuth2DecoratorFromClientSecrets(
    os.path.join(os.path.dirname(__file__), 'client_secrets.json')
    scope='https://www.googleapis.com/auth/plus')

class MainHandler(webapp.RequestHandler):
    @decorator.oauth_required
    def get(self):
        http = decorator.http()
        # http is authorized with the user's Credentials and can be
        # used in API calls

このサンプルの MainHandler に割り当てられている URL に最初にアクセスすると、例の「このアプリケーションが次の機能への……」というページにリダイレクトされ、スコープで指定された外部サービス(上記の場合は Google+ の API)とやりとりするための鍵みたいなもの(=アクセストークン)が発行される。認証フローが済んでいれば、次にアクセスしたときにはリダイレクトはされず、取得済みのアクセストークンを使って外部サービスとのやりとりがスムーズに行なえるようになる……のだが、アクセストークンには有効期限があるため、一定時間が経過すると再びアクセストークンを取得しようとする。

しかしながら、OAuth2 にはリフレッシュという仕組みがあり、アクセストークンと同時に取得したリフレッシュトークンを使えば、無駄なリダイレクトなどが挟まれることなく新しいアクセストークンを取得できる……はずなのだが、どうも上記サンプルのままだとリフレッシュトークンが取得できないのだ。

その理由はツイートに書いたとおりだが、上記サンプルを例にとれば、decorator への代入部分を以下のように変更する必要がある。

decorator = OAuth2DecoratorFromClientSecrets(
    os.path.join(os.path.dirname(__file__), 'client_secrets.json')
    scope='https://www.googleapis.com/auth/plus',
    access_type='offline',
    prompt='consent')