Google APIのOAuth2に関連したトークンの扱い方法とシーケンス

API

概要

  • 上記のシーケンスはシンプルすぎて、重要なシーケンスとアクターが漏れているので、もう少し細かくシーケンス図を書き直しました。
  • 特に、refresh tokenの取得方法と、取得タイミング、保存タイミングなどの情報がアプリケーション開発者がアプリケーションを設計する上で重要なのですが、オフィシャルドキュメントでは網羅されていませんでした。

シーケンス図

用語の整理

  • authorization code
    • 認証コード
    • これを元に、refresh tokenaccess tokenを交換する。取得したらauthorization codeは無効になる。
    • ユーザが承認した直後にしか使わない。
    • expireする。Googleでは時間の記載は無いけど、Facebookでは10分
    • 4番のシーケンスにおける、code パラメータで取得出来る。。
  • access token
    • API呼び出しに使うトークン。
    • レスポンスを見ていると、Googleでは3600秒(=60分)でexpireするトークンが発行されている。
  • refresh token
    • 基本的には、アプリケーション側で永続化しておく。
    • expireはしない。ユーザ側でrevokeできる。
    • 無効になってしまったらauthorization codeから生成する必要がある。そのために、ユーザに再び承認をもらう必要がある。この時点ではauthorization codeは存在していないはずなので。
    • 大事に保存しておく必要がある。このcredentialとアプリ情報が第三者に漏れると、権限を乱用出来てしまう。
    • 漏れた場合は、Googleに登録したアプリをのcredentialをリセットする必要があるので、他のユーザにも影響するため厳重に管理する必要がある。

メモ

  • expirationやトークンのライフサイクルに関してはこの辺りが情報ソース。
  • authorization tokenを再発行するためには、自分のGoogleのSecurity&Privacyページに手アプリの認証をrevokeする必要がある。
    • そうじゃないと、新しいaccess_tokenが発行されるだけで、認証情報はリセットされない。
    • アプリ側で生成する認証ページのURLのパラメータを変更したときには、revokeしなければ、前回と同じパラメータの認証情報のままaccess tokenなどが生成されてしまうので注意。

ソースコード

  • rubyのコードで主要なコードスニペットを書いておきます。
  • わかりやすいように値の例も書いておきます。

refresh tokenを取得できる、authorization codeを取得するコード。

  client = Signet::OAuth2::Client.new({
    client_id: ENV.fetch('GOOGLE_API_CLIENT_ID'),
    client_secret: ENV.fetch('GOOGLE_API_CLIENT_SECRET'),
    authorization_uri: 'https://accounts.google.com/o/oauth2/auth',
    scope: [Google::Apis::GmailV1::AUTH_GMAIL_READONLY, 'email'],
    redirect_uri: 'http://localhost:3000/callback',
    access_type: 'offline', # need this to get refresh_token.
  })

  # ex:
  # http://localhost:3000/callback?code=4/nQCxXval2Q8tfg0StBx81vcILZU7kEp9BMkGq27geFsSJU3fOQ8c_dMz5VW_hySmXVq0BWtJ_lopkUk66u8ZoCx&scope=openid+email+https://www.googleapis.com/auth/plus.me+https://www.googleapis.com/auth/gmail.readonly+https://www.googleapis.com/auth/userinfo.email
  client.authorization_uri.to_s

authorization codeからrefresh tokenaccess tokenを取得するコード。

  client = Signet::OAuth2::Client.new({
    client_id: ENV.fetch('GOOGLE_API_CLIENT_ID'),
    client_secret: ENV.fetch('GOOGLE_API_CLIENT_SECRET'),
    token_credential_uri: 'https://oauth2.googleapis.com/token',
  })

  client.code = "[authorization code]"
  access_token_hash = client.fetch_access_token!

  # ex:
  # ya29.GltcBh5BsH71zSz3HI_NCTTyn2W5gfn3eVUXoQU3sHkMxF-Tu3x1gL3mZSNFM_HIglSz8oKiVj-vgFB1td-UzdXaKbutynonpws-dsG4l1mnudwFiZv9Dku8Hb-X
  access_token_hash['access_token']

  # ex:
  # 1/L8qZrV5S88vOmRd9WVWxAmv4c8QJB9lMf9mqnfkZkVk
  access_token_hash['refresh_token']

refresh tokenを使って、access tokenを取得する。

  client = Signet::OAuth2::Client.new({
    client_id: ENV.fetch('GOOGLE_API_CLIENT_ID'),
    client_secret: ENV.fetch('GOOGLE_API_CLIENT_SECRET'),
    token_credential_uri: 'https://oauth2.googleapis.com/token',
  })

  client.refresh_token = '[refresh_token]'
  access_token_hash = client.fetch_access_token!

  # ex:
  # ya29.GltcBhsO9iH4fWJITG3chjNrki5vk0M1I-bFcjTsHW2Ln4CBRZjT-eGBSlf_Qa4MewfWxsrU_s-DMb4AwOrkFpgUuGegStiGKKZkiazoPU5c3JUihNzOTcdJeKKX
  access_token_hash['access_token']

コメント