👽

OAuthのリソースアクセスの分類と必要な実装

4 min read

ritou です。

今回もOAuthのClient実装における基本的なところをおさえておきましょう。

リソースアクセスの種類

OAuth 2.0に限った話ではありませんが、「リソースオーナーの代わりとしてのリソースアクセス」は次のような2種類に分類できます。というか今回はこうします。

  • リソースオーナーのセッションと同期しながら行われるリソースアクセス
  • リソースオーナーが関与しない、非同期で行われるリソースアクセス

「画像編集の対象ファイルを、SNSに投稿済みの画像から選択!ってやるとその場でユーザーの許可を得てから一覧がバーっと出てくるやつ」 が前者、「ブログの日時指定投稿時に同時にSNSにも投稿」 みたいなのが後者です。

OAuthのClient実装っていう時には、このどちらか一方、もしくは両方を実装していく必要があるわけですが、それぞれの特徴を意識しておけば実装する際に迷わないかもしれんよっていうのが今回の記事の内容です。

リソースオーナーのセッションと同期しながら行われるリソースアクセス

処理の流れはこんな感じ。

  1. 画像編集アプリにアクセス
  2. 編集対象の画像を選択する際に 「Instagramに投稿済みの画像から選ぶ」 を選択
  3. 画像編集アプリがOAuthのClientとなり、認可サーバー(AS)/リソースサーバー(RS) である Instagram からトークンを取得
  4. 取得したトークンを用いてInstagramから画像を選択し、表示
  5. 画像編集アプリを閉じたタイミングでトークンを破棄

ここでは、画像編集アプリはリソースオーナーであるユーザーのセッション内だけでリソースアクセスを行っています。

もうちょい書くと

  • リソースオーナーの関与するセッション上でアクセストークンなどを取得 する
  • リソースアクセスが一度の場合は使い捨て、同一セッション内で利用する場合はセッションに紐づけて保存してその後のリソースアクセスで利用
  • リソースアクセス失敗時は再度認可リクエストからやり直し

というような特徴があります。

当然ながらトークン類が漏洩しないようにセッションの仕組みも安全になっている必要はありますが、DB等を用いて内部のユーザーIDとトークン類の紐付けを永続的に保持する仕組みは必須ではありません。
よりセキュアにサービスを実装するためには必要以上の情報を保存しておかないことも重要です。

シーケンス図はこんな感じ。

リソースオーナーが関与しない、非同期で行われるリソースアクセス

非同期の方はと言うと...

  1. ブログサービスがFacebook連携機能を起動
  2. ブログサービスがOAuthのClienとなり、AS/RS である Facebook からトークンを取得
  3. DBなどで取得したトークンと内部のユーザーIDを紐づける
  4. ブログサービスで新規投稿や日時指定投稿が完了したタイミングで紐づけられたトークンを用いて Facebook に投稿 する

この場合、トークンの取得とリソースアクセスのタイミングが必ずしも一致しません。

  • リソースオーナーの関与するセッション上でアクセストークンなどを取得 する
  • トークンはDBなどにユーザーと紐づけて保存、リソースアクセスを行うタイミングで参照して利用
  • リソースアクセス失敗時はリソースオーナーへの通知などを行い、再びリソースオーナーから許可を得るまではリソースアクセスを停止

というような特徴となります。

シーケンス図、あんまり意味ない事に気づいたので省略します。

混在するパターン

もちろん、両方が混在するパターンもありえるでしょう。

その場合、

  • 用途によってトークン取得のフローをわけ、セッションに保存するものとDBに保存するもので実装を分ける : それぞれ単独での実装を組み合わせる
  • DBに保存しちゃえばどこでも使えて便利 : 非同期を想定した実装に寄せる

と言うような選択肢があるでしょう。

前者の方が "要件に応じて必要最小限のものを保存する" みたいなところで望ましい気はしますが、実装が必要はやや冗長もしくは複雑になる可能性はあるでしょう。
後者でも、同期/非同期それぞれで取得したトークンを別々で保存するか、たくさんの "scope" を認可リクエストに指定してトークンを取得するかのキメが必要になるでしょう。

大きいところは大丈夫だと思いますが、認可サーバー/リソースサーバーによっては同一Clientからのアクセス要求の捌き方が異なる場合があります。最初に scope=a b が指定された認可リクエストでアクセストークンを取得した後、 scope=c の認可リクエストを送って再びアクセストークンを取得した時に、たまーに最初に取得したアクセストークンのscopeも c となってしまうような実装を見たことがあります。
認可サーバー、リソースサーバーの挙動を確認した上でどのような実装にするかを検討することも大事でしょう。

仕様

ここからは主に非同期なリソースアクセスに必要なリフレッシュトークン周りの仕様を紹介します。

OAuth 2.0

  • アクセストークンと一緒にリフレッシュトークンも発行しても良い
  • grant_type=refresh_token でアクセストークンを更新

OIDC

Final: OpenID Connect Core 1.0 incorporating errata set 1 - 11. Offline Access

offline_access と言う scope を指定することでリフレッシュトークンを要求できる。

Google

Using OAuth 2.0 for Web Server Applications  |  Google Identity Platform

認可リクエストに access_type=offline を指定するとリフレッシュトークンを要求できる。

Facebook

長期トークン - Facebookログイン - ドキュメンテーション - Facebook for Developers

grant_type=fb_exchange_token を指定することで、有効期限の短いアクセストークンから有効期限の長いアクセストークンを取得できる。

curl -i -X GET "https://graph.facebook.com/{graph-api-version}/oauth/access_token?  
    grant_type=fb_exchange_token&          
    client_id={app-id}&
    client_secret={app-secret}&
    fb_exchange_token={your-access-token}" 

まとめ

  • リソースアクセスを同期/非同期で行われる2種類に分類、それぞれの実装のポイントを紹介した
  • リフレッシュトークンを要求したりアクセストークンの有効期限を伸ばすあたりの仕様が、認可サーバー/リソースサーバーによっても微妙に異なるので気をつけましょう

本投稿も含めて、最近、OAuthの主にClient実装に関する基本的な知見をZennに書いています。

https://twitter.com/ritou/status/1349326168219025409

あなたの疑問はみんなの疑問です。
質問、知りたいことがありましたらマシュマロにて匿名にて受け付けております。

https://twitter.com/ritou/status/1350240815159808003

これまでの記事もご覧ください。

https://zenn.dev/ritou

ではまた!!!