リフレッシュトークン

GoogleアカウントやLINEアカウントなどで、他のアプリのサービスを認証するOAuth 認証を使用している場合、リフレッシュ・トークンを有効にできる。

そもそもトークンとは、ユーザー・クライアントがアクションを実行すべく必要な認証情報である。一般的にjson形式で記述され、JSON Web Token (JWT) と呼ばれる。

アクセストークン(Access Token)

クライアントがリソースに直接、アクセスするために必要な認証情報を保持している。クライアントがサーバーにアクセストークンを渡して、サーバーはそのトークンの情報を解析、クライアントが認可したものかどうかを判断する。アクセストークンには、有効期間があって、存在期間は短い。

f:id:astrophysik928:20210904013807p:plain
アクセストークンの仕組み

リフレッシュトークン(Refresh Token)

リフレッシュ・トークンは、現行のアクセス・トークンが無効または期限切れになったときに新規アクセス・トークンを取得するため、または同じスコープあるいはより狭いスコープの追加アクセス・トークンを取得するために、クライアントに対して発行される。いわば、追加のアクセストークンともいえる。またリフレッシュトークンはアクセスすべきリソースがあるサーバーではなく、認証サーバーに送られる。

f:id:astrophysik928:20210904013704p:plain
リフレッシュトークンの仕組み

リフレッシュトークンは有効 期間が長いため、クライアントがサーバーからリフレッシュトークン取得後、攻撃者が使用できないように、保護する必要がある。アクセストークンも同様だが、存続期間が短いためセキュリティの考慮事項の制限も緩くなる。

OAuth 2.0では、例えばGoogleアカウントやLINEアカウントであるブログサービスなどにログインしたり記事を書いたりする時、GoogleアカウントやLINEアカウントが認証サーバーとして機能し、アクセストークンやリフレッシュトークンを発行しているという構図になる。

Curlでの通信

例えば、パスワードに 'abc' を設定したユーザー 'wang' と、client secretに 'secret' を設定した 'testclient' というクライアントがある場合、以下のように、新しいAccess Token/Refresh Tokenのペアをリクエストすることができる。

$ curl -X POST -H 'Authorization: Basic dGVzdGNsaWVudDpzZWNyZXQ=' -d 'grant_type=password&username=wang&password=abc' localhost:3000/oauth/token

{
    "token_type":"bearer",
    "access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiVlx1MDAxNcKbwoNUwoonbFPCu8KhwrYiLCJpYXQiOjE0NDQyNjI1NDMsImV4cCI6MTQ0NDI2MjU2M30.MldruS1PvZaRZIJR4legQaauQ3_DYKxxP2rFnD37Ip4",
    "expires_in":20,
    "refresh_token":"fdb8fdbecf1d03ce5e6125c067733c0d51de209c"
}

Authorization Headerには、client idとclient secretがBASE64エンコードされている。

$ curl 'localhost:3000/secret?access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiVlx1MDAxNcKbwoNUwoonbFPCu8KhwrYiLCJpYXQiOjE0NDQyNjI1NDMsImV4cCI6MTQ0NDI2MjU2M30.MldruS1PvZaRZIJR4legQaauQ3_DYKxxP2rFnD37Ip4'

This is secret

トークンが切れると・・・

$ curl 'localhost:3000/secret?access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiVlx1MDAxNcKbwoNUwoonbFPCu8KhwrYiLCJpYXQiOjE0NDQyNjI2MTEsImV4cCI6MTQ0NDI2MjYzMX0.KkHI8KkF4nmi9z6rAQu9uffJjiJuNnsMg1DC3CnmEV0'

{
    "code":401,
    "error":"invalid_token",
    "error_description":"The access token provided has expired."
}

Googleからのリフレッシュトークン取得までの流れ

スプレッドシートなどにRailsなどからアクセスしたい時、

  • クライアントID
  • クライアントシークレット
  • リフレッシュトーク

が必要である。

大まかな流れとしては、

  1. GCPにプロジェクトの作成
  2. OAuthクライアントID生成(アプリケーションの種類、名前、承認済みのリダイレクトURIは3つは入力することURlはlocal・・・などでいい)
  3. リダイレクトによる認証コードの取得


である。3では、以下のようなURLをブラウザに貼ってリダイレクト後、表示されたURLの「code=❌❌❌」の❌❌❌がリフレッシュトークンとなる。リダイレクト先では「forbidden」や「接続できません」と出るかもしれないが、それ自体は問題なく、codeの値だけわかればいい。

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly&
 access_type=offline&
 include_granted_scopes=true&
 redirect_uri=http%3A%2F%2localhost:8080&
 response_type=code&
 client_id=上記で取得したクライアントID

取得したリフレッシュトークンは環境変数などに入れて、以下のような感じで(Rubyの例)実装する。

      client_id       = args.try(:fetch, :client_id, nil)      || ENV.fetch('GOOGLE_DRIVE_CLIENT_ID')
      client_secret   = args.try(:fetch, :client_secret, nil)  || ENV.fetch('GOOGLE_DRIVE_CLIENT_SECRET')
      refresh_token   = args.try(:fetch, :refresh_token, nil)  || ENV.fetch('GOOGLE_DRIVE_REFRESH_TOKEN')

      @credentials = Google::Auth::UserRefreshCredentials.new(
        client_id: client_id,
        client_secret: client_secret,
        scope: [
          'https://www.googleapis.com/auth/drive',
          'https://www.googleapis.com/auth/spreadsheets',
        ],
        redirect_uri: 'http://localhost'
      )
      @credentials.refresh_token = refresh_token
      @credentials.fetch_access_token!
      @session = GoogleDrive::Session.from_credentials(@credentials)

参考URL

GoogleAPIのアクセストークンとリフレッシュトークン - Qiita