OAuth2.0 を調べてみた
Gmail API を利用しようとしたところ OAuth2.0 プロトコルに従い認可をしているとのことでRFC6749 を読み概要を掴んでみました。
OAuth は次のような場面で、ユーザーからリソースへのアクセスの認可を得るために利用されています。
- あるユーザー(ユーザー X)がサービス A とサービス B にアカウントを持っています。
(サービスとは、例えば Gmail・Facebook・Twitter・GitHub です。) - サービス A は、ユーザー X がサービス B に持つ情報(リソース)を利用することで動作します。
ここで、サービス A からサービス B を利用しようとすると、次のことが必要となります。
- ユーザ認証
サービス B を利用するためには、ユーザー認証を通す必要があります。
そのため、認証情報(例えば、ユーザー名、パスワード)が必要となります。 - ユーザーの認可
サービス A が、サービス B の情報を利用することに対する同意です。
ユーザーが認可したサービスだけ、 リソースの利用を可能とすることで、知らないサービスからの不正利用を防ぐことができます。
OAuth プロトコルを利用する前は、例えば次の手段で上記の対応をしていました。
- 認証情報は、予めサービスに登録しておく。
- ユーザの認可は、利用規約等で同意を得ておく。
しかし、この手段ではいくつかの問題点がありました。
- クライアント(上記例のサービス A に相当)が、認証情報を保持する必要があるため、 ユーザーにとっても、開発者にとってもセキュリティーリスクが高まる。
- リソースオーナー(上記例のユーザーに相当)がリソースに対してアクセス権を制御できない。
クライアントはユーザー認証を通しているので、リソースオーナーと同じ操作ができます。 クライアントに悪意があれば、例えばデータの改竄ができてしまいます。
クライアントに対して、アクセス権(または、スコープ)を制御できれば、リソースの参照系操作は許可するが、更新系操作は禁止するといったことが可能となります。これで、データの改竄をシステム的に防ぐことができます。
OAuth は、 認可層の導入、リソースオーナーの役割とクライアントの役割を分離することでこれらの課題に対応しています。
OAuth 2.0 Protocol フロー
OAuth2.0 の基本的なフローは次の通りです。
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
Figure 1: Abstract Protocol Flow
RFC6749(https://tools.ietf.org/html/rfc6749) より
図中のエンティティは次の通りです。
- Resource Owner(リソースオーナー)
ユーザーと同意です。
保護されたリソースに対するアクセス権を付与する役割があります。 - Client(クライアント)
リソースオーナーに代わって、保護されたリソースをリクエストするアプリケーションです。
上述したサービス A がクライアントにあたります。 - Authorization Server(認可サーバー)
リソースオーナーの認証と認可を得て、クライアントにアクセストークンを発行するサーバーです。 ここで、アクセストークンとは保護されたリソースにアクセスするための資格情報のことです。 - Resource Server(リソースサーバー)
保護されたリソースを保持しているサーバーです。
保護されたリソースへのリクエストのうち、アクセストークンを利用しているモノのみ応答します。
図中の各プロセスは次の通りです。
- (A)
クライアントが、リソースオーナーに認可(保護されたリソースに対するアクセス権)を要求します。 - (B)
リソースオーナーが認可した場合、 クライアントは、Authorization Grant(認可付与?)を受け取ります。 Authorization Grant は、リソースオーナーの認可を表す資格情報のことです。 RFC6749 では、Authorization Grant を得るためのフローを 4 通り定義しています。 - (C)
クライアントは、認可サーバーにアクセストークンを要求します。 この時に、クライアントは、 Authorization Grant を認可サーバーに渡します。 - (D)
認可サーバーは、クライアントの認証と Authorization Grant の検証をします。
検証結果が正しい場合、アクセストークンをクライアントに発行します。 - (E)
クライアントは、リソースサーバーから保護されたリソースを要求します。
この時、アクセストークンも渡すことで信頼できる要求であることを証明しています。 - (F)
リソースサーバーは、アクセストークンを検証し、正しい場合リクエストを受け付けます。
Authorization Code
OAuth2.0 では、リソースオーナーから Authorization Grant を得るためのフローを 4 通り定義していると上述しましたが、 今回は、Gmail API に使われている Authorization Code について、下記に記載します。
Authorization Code は、リダイレクトを基本としたフローであるため、 クライアントはリソースオーナーのユーザエージェント(例えば、ウェブブラウザ)とやり取りする機能が必要になります。 また、認可サーバーからリダイレクト経由でくる要求を受け付ける機能も必要です。
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
Note: The lines illustrating steps (A), (B), and (C) are broken into
two parts as they pass through the user-agent.
Figure 3: Authorization Code Flow
RFC6749(https://tools.ietf.org/html/rfc6749) より
図中の User Agent はウェブブラウザと置き換え理解して問題ないです。
各プロセスは次の通りです。
- (A)
クライアントは、リソースオーナーのユーザーエージェントを認可エンドポイント(認可処理用の URI)に導くことでこのフローを開始します。
また、クライアントは事前にクライアント ID・スコープ・ローカルステート・リダイレクト URI を認可エンドポイントのパラメータに設定しておきます。
これは、ユーザーエージェントがウェブブラウザの例で言い換えると、
まず、クライアントは認可処理用の URI のパラメータにクライアント ID・スコープ・ローカルステート・リダイレクト URI を設定した URI を生成します。
そして、生成した URI でウェブブラウザを開くということです。 - (B)
認可サーバーは、リソースオーナーをユーザエージェント経由で認証します。 そして、リソースオーナーがクライアントのアクセス要求を認可するか拒否するか確認します。 - (C)
リソースオーナーが認可したとすると、
認可サーバーは、(A)で得られたリダイレクト URI にユーザーエージェントをリダイレクトします。
このリダイレクト URI は、認可コードとクライアントにより提供されたローカルステートをパラメータに含んでいます。 - (D)
クライアントは、アクセストークンを認可サーバーへリクエストします。
リクエスト時に認可コード、(A)で利用したリダイレクト URI も渡します。 - (E)
認可サーバーはクライアントを認証し、認可コードを検証します。
また、受け取ったリダイレクト URI が(C)でクライアントにリダイレクトした URI(またはクライアントの登録情報のリダイレクト URI)と一致するか確認します(オープンリダイレクト対策)。
確認した結果が正しい場合、認可サーバーはアクセストークンと、必要ならばリフレッシュトークンを返します。
クライアントの登録と認証
クライアントの登録
RFC6749 の 2 章によると、
プロトコルを開始する前に、クライアントを認可サーバーに登録することが要求されています。
登録する内容は、次の通りです。
- クライアントタイプ
confidential と public の 2 タイプが存在しています。
クライアントの資格情報を安全に取り扱える場合は confidential、 そうでない場合は public に該当します。
例えば、クライアントがリソースオーナーの PC 上で動作するウェブブラウザアプリケーションの場合は、public になります。 - クライアントへのリダイレクト URI
- 上記以外で認可サーバーで必要となる情報(アプリケーション名、ウェブサイト、アプリケーションの説明など)
- クライアント ID
認可サーバーは登録されたクライアントに対して、ユニークな ID を割り当てます。
これは認可リクエストやアクセストークンリクエストのパラメータとして利用されます。
クライアントの認証
RFC6749 の 3.2.1 章によると
クライアントタイプが、confidential、または、クライアント資格情報が発行されているクライアントは、
アクセストークンリクエスト時に認可サーバで認証されなくてはなりません。
クライアント認証の用途は次の通りです。
- 認可コード、リフレッシュトークンとそれらが発行されたクライアントを対応づける。
- 不正なクライアントを無効化する。