概要
- Using OAuth 2.0 to Access Google APIsにシーケンスがかいてあります。
- 上記のシーケンスはシンプルすぎて、重要なシーケンスとアクターが漏れているので、もう少し細かくシーケンス図を書き直しました。
- 特に、
refresh tokenの取得方法と、取得タイミング、保存タイミングなどの情報がアプリケーション開発者がアプリケーションを設計する上で重要なのですが、オフィシャルドキュメントでは網羅されていませんでした。
シーケンス図
用語の整理
authorization code- 認証コード
- これを元に、
refresh tokenとaccess 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 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.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']




Comments