Ari-Press

エンジニアAriのブログ.

OAuth2.0の備忘録的まとめ

OAuth2.0

Web系技術を学ぶ上で,やはりセキュリティ周りの技術は外せません。OAuth1.0ならばTwitter APIを触っていたんですが、、いつの間に2.0に!ということで、頑張って仕様書を読みつつ自分なりにまとめてみました。

The OAuth 2.0 Protocol draft-ietf-oauth-v2-10 を参考にしています。

また、以下で特に明示されない引用部分は全て The OAuth 2.0 Protocol draft-ietf-oauth-v2-10 から引用したものとします。

更に、以下の文章は2012/12/28時点でのAriの理解をまとめたものであり、内容を保証するのはこの時点でのAriの読解力のみです。

OAuth2.0の必要性

通常、ログインが必要なサービスを利用する際はログインID/パスワードの情報が必要になります。

特定のWebサービスに必要な時にアクセスするブラウザベースのアプリケーションなどではあまり問題にならないのですが、例えばモバイルアプリから定期的にアクセスをしたいような場合、パスワードなどデリケートな情報をモバイル上に保存しておく必要があります。

更にサードパーティアプリからログインが必要な通信を行う場合(例えばTwitterクライアントなど)などでは、要するに本家のWebサービスと別の会社にログインIDとパスワード両方の情報を預けることになります。これはセキュアとは言えませんよね。

一つでも悪意のあるアプリケーションでログインを行なってしまった時にユーザの自衛手段がパスワードの変更のみになってしまうのも問題になります。

そこで、ユーザの利便性を損なわずに、かつセキュアにログイン情報を本家のWebサービスとやり取りする仕組みが必要になります。このためによく使われるのがOAuth2.0ということになります。

どうやって上記の要件を満たすのか?

これは要するに、各ユーザについて、利用するアプリケーションごとに「アクセストークン」を発行し、認可を受けた情報を見れるようにしましょうということです。

アクセストークンとは?どうやって発行するのか?など、下で詳しく見ていきます。

アクセストークンとは

アクセストークンはクライアントがリソースオーナーに変わって認証済みリクエストを行うために使われるトークン.と定義されています。あるWebサービスに対してログインが必要な通信を行うときに、「これは認証済みのユーザによって認可されたリクエストだ」ということを保証するためのトークンですね。

アクセストークンは以下のようにHTTPヘッダに埋めこまれ、本家Webサービスで情報の照会が成功すると保護されたコンテンツの取得が返されるという仕組みです。

1
2
3
GET /resource HTTP/1.1
Host: server.example.com
Authorization: OAuth vF9dft4qmT

このアクセストークンはユーザが本家Webサービス上で許可を与えたアプリケーションに対して個別に与えられるものなので、悪意のあるアプリケーションにログイン情報を与えてしまったとしても、そのアプリケーションに対する許可(アクセストークンの使用許可)を取り消してしまえばパスワードの変更なしにユーザの自衛が可能になります。

アクセストークンの取得

さて、このアクセストークンを発行するにはどうするか?

アクセストークンを取得する際にはやはりユーザのログインID・パスワードが必要になります。これらのログイン情報をアプリケーションに渡すことなくアクセストークンを発行する必要があるので、発行のプロセスはある程度複雑になっています。

このプロセスを以下に示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  +--------+                                  +---------------+
  |        |--(A)-- Authorization Request --->|   Resource    |
  |        |                                  |     Owner     |
  |        |<-(B)------ Access Grant ---------|               |
  |        |                                  +---------------+
  |        |
  |        |         Client Credentials &     +---------------+
  |        |--(C)------ Access Grant -------->| Authorization |
  | Client |                                  |     Server    |
  |        |<-(D)------ Access Token ---------|               |
  |        |      (w/ Optional Refresh Token) +---------------+
  |        |
  |        |                                  +---------------+
  |        |--(E)------ Access Token -------->|    Resource   |
  |        |                                  |     Server    |
  |        |<-(F)---- Protected Resource -----|               |
  +--------+                                  +---------------+

上の図を一段階ずつ説明していきます。

クライアント個別の認証

ここで言う「クライアント」は、例えばサードパーティアプリを想像してください。本家Webサービスのログイン情報を受け取ることなく、アクセストークンを取得したいアプリケーションのことです。

アクセストークン発行の過程でまず必要になるのは、あるリクエストが特定のクライアントからのものであるということを証明することです。悪意のあるクライアントが別の信頼できるクライアントに偽装できてしまったら意味がないので。

このため、まず事前にクライアントを本家Webサービス上に登録しておき、クライアント固有のIDとパスワードを発行しておきます。これは上記プロセス図の( A )と( B )に対応します。

このIDとパスワードを( C )のリクエストに付与する必要があるのですが、ここではHTTPの Basic認証 を利用してクライアントの認証を行います。

1
2
3
4
5
6
7
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_id=s6BhdRkqt3&code=i1WsRn1uB1&
redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

また、代替方法として以下のようなリクエスト方法も可能です。

1
2
3
4
5
6
7
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_id=s6BhdRkqt3&
client_secret=gX1fBat3bV&code=i1WsRn1uB1&
redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

エンドユーザによる認可

次に、クライアントが保護されたコンテンツに対してアクセスすることについてのエンドユーザの同意を確認しなければなりません。

このために、ユーザが一度ブラウザを使って本家WebサービスにアクセスしてログインIDとパスワードを入力してログインし、更にクライアントによる保護されたコンテンツの利用を許可します。これは、ユーザがブラウザ上で特定のURLにアクセスするようにクライアントが誘導することで実現します。

ここでブラウザでログインを行う理由は、ログインIDとパスワードの入力にクライアントを介さないためです。クライアント上で入力してしまうとログインIDとパスワードを抜けてしまうので危ない、ということですね。

以上により、クライアントは認可コードを得ることができます。これはプロセス図での( A )と( B )に対応します。

この際ユーザにブラウザ上でアクセスさせるURLは以下のようなものになります。

1
2
3
GET /authorize?response_type=code&client_id=s6BhdRkqt3&
    redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

※このリクエストには「クライアント個別の認証」で示したようなクライアントの認証情報は付加しません。

また、ここでredirect_uriを指定していますが、これはユーザがクライアントの認可後にリダイレクトを指示されるURIです。これは例えばiOSアプリケーションの場合、カスタムスキーマを指定しておくことでクライアントアプリケーションにユーザを再度誘導することが可能です。

以上のような流れでエンドユーザによる認可が得られた場合、アクセストークンがパラメータとして付加された状態で指定したredirect_uriに対してユーザからのリクエストが行われ、クライアントは認可コードを得ることができます(ここはresponse_typeによりますが、認可コードの取得を指定した場合)。

アクセストークンの取得

さて、ここまでで取得した

  • 認可コード
  • クライアント個別の認証情報

を利用して、ようやくアクセストークンの取得を行います。

アクセストークンの取得は、例えば以下のようなリクエストで行います。

1
2
3
4
5
6
7
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_id=s6BhdRkqt3&code=i1WsRn1uB1&
redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

ユーザからの認可情報である認可コードcodeをクライアント個別の認証情報と共に本家Webサービスに送り、情報を付きあわせた結果リクエストが正当であれば、アクセストークンを得ることができます。

この結果取得できるレスポンスは以下のようなものです。

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token":"SlAV32hkKG",
  "expires_in":3600,
  "refresh_token":"8xLOxBtZp8"
}

ここで得られたaccess_tokenを端末なりに保存しておき、以降のリクエストに利用することでユーザのログインID・パスワードを受け取ることなく保護されたコンテンツにアクセスすることができるようになる、ということですね。

アクセストークンの利用

このアクセストークンはHTTPリクエストヘッダに付加することで利用します。例えば以下の様なものです。

1
2
3
GET /resource HTTP/1.1
Host: server.example.com
Authorization: OAuth vF9dft4qmT
1
2
GET /resource?oauth_token=vF9dft4qmT HTTP/1.1
Host: server.example.com

これで正しいレスポンスが返ってくるはずです!

アクセストークンの生存期間

ここで改めて考えてみると、アクセストークンはそれが有効である限り認可された操作に関する権限をクライアントに与え、これは認可範囲によっては重要な情報の取得/変更も含むこともできます。これを含んだリクエストを延々と投げ続けて果たして安全なのか?

これはやはり危険で、ある条件下では盗聴や解読が可能になるため、通常生存期間がかなり短く設定されます(30分間など)。

しかし、30分毎にログインIDやパスワードを入力する手間があるとなるとユーザ体験が大幅に劣化することになります。ここで、これを避けるために利用されるのがaccess_tokenと共に取得されたrefresh_tokenです。

refresh_tokenは、一度アクセストークンを取得したクライアントが、アクセストークンを再取得するために利用されるもので、具体的には以下のように使用されます。

1
2
3
4
5
6
7
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&client_id=s6BhdRkqt3&
refresh_token=n4E9O119d

この結果、ユーザからの認可情報を渡した場合と同等のレスポンスが返却されます。

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token":"SlAV32hkKG",
  "expires_in":3600,
  "refresh_token":"8xLOxBtZp8"
}

以降のアクセスはここで再取得されたアクセストークンを利用して行う、という流れになります。

以上、これでOAuth2.0での認可を得たリクエストが可能になります。やった!

追記

以上のような OAuth2.0 ですが、ご利用は計画的にという話。確かに・・・!(別ブログさんより)

単なる OAuth 2.0 を認証に使うと、車が通れるほどのどでかいセキュリティー・ホールができる

僕も気をつけます。ついでに「認証」って言葉はまずい気がしたのでちょいちょい書き換えました。

再追記 (2015/9/6)

認証/認可の使い方をまた少し修正したのと、アクセストークンの有効範囲に対する言及を修正しました。Nat Sakimuraさんご指摘ありがとうございました!

Comments