はじめに

何かを知ろうと思ったときに、まずそれが一体何者なのかといった概要を掴まないことには一体全体何の話をしているのかわけがわからないことはよくある。

自分の中でOAuthもその類だった。

このブログ自体も、Netlify で運用しているが、GitHubからコードを自動でとってきている。

連携の構築の段階で、GitHubに対して、Netlifyからのアクセスを許可するか?みたいな問いに答えた記憶がある。ここら辺の仕組みを理解しておきたいと思う。

これを書くにあたっては主に以下を参考とした。

RFC 6479: The OAuth 2.0 Authorization Framework

RFC 6749: The OAuth 2.0 Authorization Framework 日本語訳

また、本文の中で特段の記載なく引用されている文章があれば上記RFCからの引用である。

OAuthとは

OAuth はリソース保有者に代わってクライアントがリソースにアクセスする手段を提供する仕組みの1つ。

OAuth provides a method for clients to access server resources on behalf of a resource owner

RFC 5849: The OAuth 1.0 Protocol

例えば、特定のリソース(Google Photosの写真やGitHub上のソースコード等)を別のアプリケーションから利用したい場合どうするか?

リソース保有者が写真やソースコードを手動でアップロードしてそのアプリケーションに使わせるというのがまず思いつく方法だ。少しだけならでもそれでもいいけど、大量だったり、定期的に行われる場合そんなめんどくさいことはやってられない。

OAuthがない世界の話

昔ながらの手法としては、利用したいアプリケーションに特定のリソースへアクセスできるアカウントのログイン情報を記憶させて、人がログインする代わりにアプリケーションがログインしてリソースを取得するような方法が挙げられる。

いやぁ、そういうことやってるツールあるよねぇという気になったりするけど、以下のような問題が考えられなかなかと悪しき手法である。

  • そのアプリケーションは信頼できるの?そいつが悪さして情報抜き取ったりしない?
  • 悪気なくても、アプリケーションにバグあって誤ってファイル削除しちゃったりしない?
  • 後からログ見たときに、人の操作なのかアプリの操作なのかわからないと調査困らない?

代理でログインする形だとログイン者の権限と同じ操作を行えてしまう。

アプリ的にはデータのアップロードしか必要ないのに、権限上データのダウンロードができてしまうとアプリの開発者に悪意があると不要なデータにアクセスされてしまう。

アプリ的にはデータの取得しか必要ないのに、データの削除までできてしまうと間違って消してしまうことも起こりうる。

OAuthがある世界の話

OAuthは悪しき手法の問題を解決してくれる。

Google Photosの写真やGitHub上のソースコード等のリソースを保有しているユーザが、当該のリソースにアクセスしたいアプリケーションである自作のアプリや、Netlifyみたいなホスティングサービス等に対して特定の操作のみを許可する形でアクセスする権利を委譲することができる仕組み、それがOAuthである。

だが、自分のツールだけ頑張ればいいという話ではない。リソースを提供する側のサービスがOAuthに対応していない話にならない。

社内システムに対してOAuthに則ってアクセスしたいと思ってもそもそも社内システムがOAuthをサポートしていなければ話にならない。

逆にGoogle Photos の場合、OAuth 2.0以外サポートされていないので、否応なしに従わざるをえない

Your application must use OAuth 2.0 to authorize requests. No other authorization protocols are supported.

Google Phots APIガイド Authentication and authorization scopes

OAuthの読み方

脳内で音読している人はOAuthをどう読んでいるだろうか。

個人的には、Oの発音はハッキリして、 オゥオース という読み方がクールだと思っている。

OAuth 1.0 と OAuth 2.0

OAuthには OAuth 1.0OAuth 2.0 がある。

端的にいえばもともと OAuth 1.0 という仕様があり、その課題を解決するように登場したのが OAuth 2.0

正直、 OAuth 1.0自体についてはほぼ何も知らないけれど、「OAuth 2 in action」の中では以下のような違いが述べられていた。

In OAuth 1.0, there was only one method for getting an access token that all clients had to use. It was designed to be as general purpose as possible, attempting to cater to a wide variety of deployment options. As a consequence, the protocol was not particularly well suited to any use case.

OAuth 1.0では、アクセストークンを取得する方法を1つだけ提供していて、様々なケースに対してできるだけ汎用的に利用できるようにしていたらしい。ただ、結果として(汎用的であるがゆえに)どのケースにもうまくフィットしなかったらしい。

When OAuth 2.0 was being developed, the working group made a distinct decision to treat the core protocol as a framework instead of a single protocol. By keeping the core concepts of the protocol solid and allowing extensions in specific areas, OAuth 2.0 can be applied in many different ways.

(OAuth1.0の失敗を踏まえて)OAuth 2.0は、コアとなるフレームワークとして利用できるように設計して、個々の詳細については拡張できるようにしたらしい。結果として、色々なケースでOAuth2.0を利用できるようになったと。

OAuth 1.0の後継として登場したOAuth 2.0ではあるが、ドキュメントの中で述べられているように OAuth 2.0はOAuth 1.0とはほぼ違うらしく、OAuth1.0のことは忘れてドキュメントを読めってRFCにも書いてあるので注意が必要。

The OAuth 2.0 protocol shares very few implementation details with the OAuth 1.0 protocol. Implementers familiar with OAuth 1.0 should approach this document without any assumptions as to its structure and details.

OAuth1.0 について学ぶつもりはほぼないので、以降「OAuth」という単語は基本的に「OAuth 2.0」を意味するものとして利用する。

OAuthと認証・認可の関係

RFC 6479: The OAuth 2.0 Authorization Framework

RFCのタイトルになっている通り、OAuth 2.0 は Authorization、つまり認可のためのフレームワークである。

認可は認証とセットで出てくることが多い。認証と認可は全く別物。

ここでいう認証は Authentication であって SSL/TLS証明書等ででてくるCertification のことではない。

認証と認可について以下の記事がわかりやすくまとまっていていいなぁと思った。

[Developers.IO]よくわかる認証と認可

また、認証・認可等、ID管理全般については、IPAの公開している以下の資料がまとまっており参考になる。

[IPA]アイデンティティ管理技術解説

OAuthの仕組み

登場人物

OAuthには役割として以下の4つの主体が登場する。

  • リソースオーナー(resource owner)
  • リソースサーバ(resource server)
  • クライアント(client)
  • 認可サーバ(authorization server)

リソースオーナー(resource owner)

リソースへアクセスする権利を持っている主体。通常、それを管理している人を指す。

具体的には、Google PhotosやGitHubのアカウントを持っているあなた自身を指す。

リソースサーバ(resource server)

リソース(写真やソースコード等)を保持しているアプリケーションを指す。

具体的には、Google PhotosやGitHubが該当する。

リソースについて、アクセスする権利を持っていないアクセスできないことを強調するために保護リソース(Protected Resource)という言い方もする。

クライアント(client)

保護されたリソースを利用したいアプリケーション等。Webアプリだったりネイティブアプリだったり様々。

具体的には、Google Photoの自分の写真にアクセスしたい自作のアプリや、GitHubのソースコードにアクセスしたいNetlify。

認可サーバ(authorization server)

OAuthの中で一番多くの仕事をこなすのはこいつだ。

クライアントに対して、リソースオーナーの判断に基づいて、認可を行うためのアプリケーション。認可されればクライアントに対して **アクセストークン(access token)**を発行や、アクセストークンを再発行するための リフレッシュトークン(refresh token) の発行等、OAuthの仕組みの要となる役割を持つ。

概念上はリソースサーバとは別物だが、クライアントから見るとリソースサーバと認可サーバは同じアプリケーションに見えることが多い。(GitHub等もリソースサーバでありつつ、自身のリソースに対するアクセストークンを発行する認可サーバでもある)

GitHubのリソースサーバとしてのエンドポイント: https://api.github.com/

GitHubの認可サーバとしてのエンドポイント :https://github.com/login/oauth/

登場する重要パーツ

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

こいつがOAuthの肝となる部分だ。

アクセストークンは、クライアントからリソースサーバに対して提示するクレデンシャルである。

Access tokens are credentials used to access protected resources. An access token is a string representing an authorization issued to the client. The string is usually opaque to the client. Tokens represent specific scopes and durations of access, granted by the resource owner, and enforced by the resource server and authorization server.

アクセストークンはクライアントからすると特に意味を持つ文字列ではない。その文字列はリソースサーバに渡して初めて意味を持つ。

アクセストークンは認可情報を参照するために識別子として利用したり、それ自体に認可情報を含ませることもできる。

The token may denote an identifier used to retrieve the authorization information or may self-contain the authorization information in a verifiable manner (i.e., a token string consisting of some data and a signature).

識別子として利用するということは実際の認可情報は認可サーバの中に保管されていて、リソースサーバから認可サーバのDBへの認可情報の検索のためのキーとして利用するということだろう。

反対に、それ自体に持つということは別にデータベースがあるわけではなく、アクセストークン自体にスコープや有効期限を持たせることもできるようということである。

なお、アクセストークン自体に認可情報を持たせる場合は、JSON Web Token (JWT)が利用されるようである。

クライアントID/クライアントシークレット(Client ID/Client Secret)

クライアントID・クライアントシークレットは、認可サーバがクライアントを認証する際に利用する。

通常、事前にクライアントを認可サーバに登録する必要があり、クライアントを登録した際にクライアントIDとクライアントシークレットが発行されることが多い。

GitHubにおいてもクライアントの登録時に発行されるようである。

アクセストークンスコープ(Access Token Scope)

クライアントが許可されているリソースへのアクセスできる範囲を示すものである。

認可サーバへアクセスの許可を求める時点で、クライアントからクライアントが利用したい権限に提示するためにも利用される。

クライアントから指定する場合、事前に自身の利用したいスコープを把握しておく必要がある。

リダイレクトエンドポイント(Redirection Endpoint)

認可サーバからレスポンスを返す際にリダイレクト先となるクライアントのエンドポイントを示すために利用される。

通常、認可サーバにクライアントを登録する際にクライアントのエンドポイントを提示することが求められる。

ここで複数のエンドポイントを登録している場合、 redirect_uri パラメータを利用してリクエスト時に指定することもできる。

GitHubのクライアント登録ページでは、Authorization callback URL と記載されている。 https://github.com/settings/applications/new

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

今回は触れないが自分の備忘として記載。

リフレッシュトークンは、アクセストークンを更新するためのものでありクライアントから認可サーバへ向けて提示するクレデンシャルである。

アクセストークンは、漏洩などによる任意のタイミングだったり有効期限が設けられていたりで無効化されることがある。

こうした際に、リソース保有者への確認を行うことなく自動でアクセストークンを再取得するためにリフレッシュトークンが用意されている。

ここで疑問に思うのは、せっかくアクセストークンを無効にしたのに、また勝手にアクセストークンが発行されてよいのか?ということである。リフレッシュトークンごと漏洩していた場合、無意味なのではないかと想像できる。

リフレッシュトークンを利用したアクセストークンの再取得にはクライアントの認証が必要となる。仮にリフレッシュトークンが漏洩して、悪意あるクライアントがリフレッシュトークンを利用して、アクセストークンを入手しようとしても、クライアント認証の壁に阻まれるわけである。

認可付与(Authorization Grant)のための4つのタイプ

OAuthの中で認可を付与する仕様として4つのパターンが仕様として定められている。

  • 認可コード(Authorization Code)
  • インプリシット(Implicit)
  • リソースオーナーパスワードクレデンシャル(Resource Owner Password Credentials)
  • クライアントクレンデンシャル(Client Credentials)

クライアントがWebサーバを伴うアプリなのか、ブラウザ内だけでほぼ完結するアプリなのか、ネイティブアプリなのか等によって適したパターンが異なり、各仕様によって流れや利用する項目が若干異なる。

(例えばインプリシットだと前述のリフレッシュトークンは登場しない)

GitHubは スタンダード な**認可コード(Authorization Code)**の付与タイプをサポートしているらしいので、まずはこのタイプからみていく。

認可コード(Authorization Code)付与タイプの流れ

このタイプでは大きく3つのステップに分かれる。

  1. クライアントが認可サーバからの認可コードを取得する
  2. 認可コードを利用してクライアントが認可サーバからのアクセストークンの取得する
  3. アクセストークンを利用してクライアントが保護されたリソースを操作する

シーケンス図を描いたので以下に貼っておく。

(シーケンス図は PlantUMLを利用して描いている)

(自分が)混乱しないようにリソース保有者とユーザエージェント(図の中ではWebブラウザ)を分けたり、クライアントはWebアプリとしている。

また、(自分が)わかりやすいように、通常のHTTPリクエスト/レスポンスと、OAuthならではのリダイレクトも分けて書いている。

どこでリダイレクトしているという部分を認識しておくのは大事だと思っていて、リダイレクトが故にGETか使えずクエリ文字列が必要になることがわかるし、リダイレクトするが故にWebブラウザにはその情報が筒抜けになってしまうことが理解できる。

AuthorizationCodeFlowコード

0.事前準備

シーケンス図には登場しないが、フローが始まる前にはクライアントを認可サーバに登録するという作業が必要になる。

Auth0をクライアントとして、GitHubに登録する方法が載っているのでこれを参考に説明することにする。(ありがたい)

[Auth0] Connect your app to GitHub

気にするべきは、GitHubにクライアントを登録した時に発行された Client IDClient Secret をクライアント(Webアプリ)に登録する必要があるという部分。

GitHubの場合、このパラメータをクライアント認証で利用しているので、事前に登録しておかないとこの後のフローので認可サーバがクライアントを認証できない。

RFCにはクライアント認証について以下の記載があり、Basic認証をサポートするかリクエストボディにクレデンシャル含んだ認証をサポートするかの必要があるらしい。GitHubの場合、後者を選択しているものと思われる。

The authorization server MUST support the HTTP Basic authentication scheme for authenticating clients that were issued a client password.

For example (with extra line breaks for display purposes only):

Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3

Alternatively, the authorization server MAY support including the client credentials in the request-body using the following parameters:

client_id REQUIRED. The client identifier issued to the client during the registration process described by Section 2.2.

client_secret REQUIRED. The client secret. The client MAY omit the parameter if the client secret is an empty string.

1.クライアントが認可サーバからの認可コードを取得する(認可リクエスト/レスポンス)

保護されたリソースへのアクセス・操作が必要であることがわかれば、クライアントは保護されたリソースへアクセスするためのフローを開始する。

認可コードのフローの場合認可コードをアクセストークンと交換するという処理が必要であり、まず認可コードを取得する流れがある。

クライアントが認可コード取得のためには以下のステップがある。

1-1. クライアントのページから認可サーバへのページへリダイレクトし、認可サーバの画面を表示する 1-2. リソース保有者が、クライアントに対してリソースへのアクセスを許可するのか否かを判断する 1-3. 判断の結果を認可サーバに送り、Web アプリのエンドポイントへリダイレクトする 1-4. クライアントは認可コードを手に入れる

1-1. クライアントのページから認可サーバへのページへリダイレクトし、認可サーバの画面を表示する

保護されたリソースへのアクセスが必要であることがわかったクライアントは、Webブラウザへレスポンスを返す。このとき、HTTPステータスコード302が指定され、Locationヘッダには認可サーバのエンドポイントに対して、クエリ文字列で client_id等が指定されたレスポンスとなっている。

レスポンスを受け取った後は、WebブラウザはLocationにで指定された内容に基づいて、認可サーバへGETリクエストを送る。

1-2. リソース保有者が、クライアントに対してリソースへのアクセスを許可するのか否かを判断する

認可サーバの画面では、保護されたリソースへのアクセスを許可するかどうかリソース保有者が判断することになる。

github_authorize

1-3. 判断の結果を認可サーバに送り、Web アプリのエンドポイントへリダイレクトする

許可した場合認可サーバから認可コードが発行されWebブラウザにレスポンスが返る。

このときも、HTTPステータスコード:302が指定され、Locationヘッダにはクライアントのリダイレクトエンドポイントに対して、クエリ文字列で認可コード等が指定されたレスポンスとなっている。

レスポンスを受け取った後は、WebブラウザはLocationにで指定された内容に基づいて、クライアントへGETリクエストを送る。

1-4. クライアントが認可コードを手に入れる

リダイレクトされたGETリクエストを受け取ったクライアントは、クエリ文字列から認可コードを取得することができる。

2.認可コードを利用して、クライアントが認可サーバからのアクセストークンの取得する(アクセストークンリクエスト/レスポンス)

認可コード取得後はリソース保有者の判断は不要のためクライアントと認可サーバとのやり取りになる。

4.1.3. Access Token Request

アクセストークンを取得するリクエストに当たって、認可コードに加え、認可コードの取得のためにRedirect URIを利用した場合にはそれも含める必要があることや、クライアントのタイプがconfidentialである場合等は、認証情報も含めるよう書かれている。

If the client type is confidential or the client was issued client credentials (or assigned other authentication requirements), the client MUST authenticate with the authorization server as described in Section 3.2.1.

5. Issuing an Access Token

認可サーバにおいてリクエストが処理され問題なければ、レスポンスにAccess Token が含まれて返される。このとき、アクセストークン有効期限やReflesh Tokenが一緒に返されることもある。

3.アクセストークンを利用して、クライアントが保護されたリソースを操作する

7. Accessing Protected Resources

クライアントが保護されたリソースにアクセスを行う場合発行されたアクセストークンをリクエストに含める。

リソースサーバはアクセストークンの検証を行い(有効期限や要求しているリソースと許可しているスコープとの妥当性等)問題なければ要求された操作を実施する。

おわりに

調べていると長かったがOAuth2の全体的な理解は進んだ。認可コードフローの流れはおおよそわかったが、他のフローについても流れをまとめたい(続く…かも)

参考

RFのC 6479: The OAuth 2.0 Authorization Framework

RFC 6749: The OAuth 2.0 Authorization Framework 日本語訳

RFC 7519: JSON Web Token (JWT)

[OpenIDファウンデーション・ジャパン] 公開資料

[Developers.IO]よくわかる認証と認可

[IPA]アイデンティティ管理技術解説

OAuth 2 in action

Googole Phots APIガイド Authentication and authorization scopes

[Build Insider] OAuth 2.0の代表的な利用パターンを仕様から理解しよう