OAuth 2.0 の仕組みと認証方法

公開日:
更新日:
0OAuth 2.0

OAuth 2.0 とは



映画「フェリスはある朝突然に」では、駐車場係がフル整備された 1961 年もののフェラーリを勝手に乗り回すシーンが登場します。あなたの新車のマスタングが同じ目に遭わないようにするにはどうすればよいでしょうか。最近の車には、駐車場係に認める操作を制限して、トランクを開けたりスピードを出して運転出来ないようにした特殊な鍵が付いていることがあります。

OAuth が生まれたのは、オンラインにおいて、これと本質的に同じ問題を解決するためです。

OAuth の誕生
(参考:Ryan Boyd(著)「OAuth 2.0 をはじめよう」)


通常、オンラインのサービスを利用するためには、ログイン ID とパスワードを組み合わせた情報 "クレデンシャル" で認証するログイン認証になります。しかし、近年 Twitter と Facebook が連携するように、異なる Web サービスの連携が進んでおり、デジタルアイデンティティの共有が問題となっていました。例えば Twitter にユーザーの個人情報があるとき、Twitter と Facebook が連携し、Twitter にある個人情報を Facebook が自由にアクセスできる状況は好ましくありません。そのため、Web サービスへのアクセスを認可する手段が必要とされていました。


例えば、悪意のあるアプリケーションが、ユーザに対してクレデンシャルを要求した場合、ユーザの個人情報が漏洩する危険があります。OAuth を使うと、他の Web サービスと連携するためユーザにクレデンシャルを譲り渡すという要求をしなくても、ユーザデータにセキュアにアクセスできるようになります。


OAuth はリソースオーナーの代わりに保護リソースにアクセスする方法をクライアントに提供します。クライアントが保護リソースにアクセスする前に、クライアントはまずリソースオーナーの認可を取得し、アクセス許可とアクセストークン (権限の範囲と期間を示すトークン) を交換しなけばなりません。クライアントは、リソースサーバにアクセストークンを渡すことにより、保護リソースにアクセスできるようになります。

用語定義

OAuth における用語定義を以下に示します。

OAuth における用語定義
用語説明
保護リソースアクセスが制限されたリソース。OAuth 認証済みリクエストを利用して取得が可能。
リソースサーバ保護リソースに対するリクエストを受付け、レスポンスを返すサーバ。
クライアント認可を取得して、保護リソースに対するリクエストを行うアプリケーション。
リソースオーナー保護リソースへのアクセスを許可するエンティティ。
エンドユーザリソースを所有するユーザ。
トークンクライアントに対して発行されたアクセス認可を表す文字列。
リソースオーナーによって許可された権限の範囲とアクセス期間を表す。
アクセストークンクライアントがリソースオーナーに代わり認証済みリクエストを行うために使われるトークン。
リフレシュトークンクライアントがリソースオーナーの新しいアクセストークンを得るために使われるトークン。
認可コード生存期間の短いトークンで、エンドユーザーによって提供されたアクセス許可を表す。
認可コードはアクセストークンとリフレッシュトークンを得るために使われる。
認可サーバリソースオーナーの認証と認可の取得が成功した後、トークンを発行するサーバ。
エンドユーザー
認可エンドポイント
認可サーバの HTTP エンドポイントで、エンドユーザーの認証と認可の取得を行う。
トークンエンドポイント認可サーバの HTTP エンドポイントで、トークンの発行とリフレッシュを行う。
クライアント識別子認可サーバがクライアント自身を識別するために発行される一意な識別子。

OAuth 1.0 と OAuth 2.0 の仕組み

OAuth 1.0 での認証は、以下の流れで行われます。


OAuth 1.0 の認証フロー
OAuth 1.0 の認証フロー

OAuth 1.0 では、クライアントとサービスプロバイダの間で何度もリクエストのやり取りが行われます。しかし、上記の認証フローでは、以下の 3 つの問題点があります。

  • 認証と署名
  • デスクトップ/モバイルアプリ対応
  • スケール時のパフォーマンス

認証と署名の問題点は、認証フローが複雑な点です。OAuth クライアントを作成するためには OAuth ライブラリが必要でしたが、複雑なためにバグが存在することもありました。また、ライブラリが存在しない言語で OAuth を使用した開発は非常に困難であるため、使用する言語が限られていました。


デスクトップ/モバイルアプリ対応の問題点は、OAuth 1.0 は Web アプリのみを対象としていていたため、デスクトップ/モバイルのアプリには対応していない点です。そのため、Twitter はデスクトップ/モバイルのアプリのために "xAuth" という独自の OAuth 拡張を取り入れました。


スケール時のパフォーマンスの問題点は、保護されたリソースにアクセスする際にクライアントクレデンシャルと、トークンクレデンシャルが必要になり、この 2 つの分離が難しいため、サーバをスケールするのが困難になる点です。一般的には、認証サーバにクレデンシャルの発行を任せて、API の応答は別サーバで行われますが、クレデンシャルの分離が困難である点がサーバをスケールするときの問題点となります。


これらの問題点を解決するために OAuth 2.0 での認証は、以下の流れで行われるようになりました。


OAuth 2.0 の認証フロー
OAuth 2.0 の認証フロー

上記のフローでは、以下のステップを踏みます。


クライアントは、リソースオーナーに認可を要求します。クライアントは直接リソースオーナーのクレデンシャルを要求すべきではありません。そのため、クライアントはアクセス許可を発行する認可サーバにリソースオーナーをリダイレクトさせます。クライアントがリソースオーナーのクレデンシャルを求める場合は、高度な信用があるときのみ利用するべきです。


クライアントは、リソースオーナーの認可を表すアクセス許可を取得します。アクセス許可は、以下のように表されます。

アクセス許可を表す用語
用語説明
認可コード認可サーバ経由で取得したアクセス許可。
アサーション異なるトラストフレームワークを利用して取得したアクセス許可。アサーションは、クライアントがアクセストークンを取得するために既存のトラストリレーションシップを利用することを可能にします。それらは OAuth と他のトラストフレームワークのブリッジを提供します。
リソースオーナー
パスワードクレデンシャル
直接リソースオーナーとクレデンシャルをやり取りをするときに使用されます。ただし、リソースオーナーパスワードクレデンシャルは、リソースオーナーとクライアントの間に高度な信用があるときのみ利用するべきです。

クライアントは、認可サーバに対して自身を認証し、アクセス許可を提示することでアクセストークンを要求します。


認可サーバは、クライアントクレデンシャルとアクセス許可の正当性を確認し、アクセストークンを発行します。オプションでリフレッシュトークンを発行する場合もあります。リフレッシュトークンを利用することで、アクセストークンの有効期限が切れた場合でも、リソースオーナーに再度アクセス許可を要求することなく新しいアクセストークンが取得できます。


クライアントは保護リソースへのリクエストを実行し、アクセスを得るためにアクセストークンを提供します。


リソースサーバはアクセストークンの正当性を確認し、有効な場合はリクエストを処理します。

クライアントの認証

アクセストークンの発行には、特定のクライアントからのリクエストであることを証明することが必要です。悪意のあるクライアントが別のクライアントに偽装してリクエストを送らないように、リソースオーナーにクライアントを登録しておきます。クレデンシャルを認可サーバに送る際、HTTP の Basic 認証を利用してクライアントの認証を行います。


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
 Basic 認証を利用してクライアントを認証する例

また、代替方法として "client_secret" にクライアントパスワードを含める以下のようなリクエストも可能です。


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
 クライアントを認証する代替方法の例

エンドユーザ認可の取得

クライアントが保護されたコンテンツに対してアクセスすることについて、エンドユーザの同意が必要になります。ユーザはクライアントにログインし、保護されたコンテンツのアクセスを許可する必要があります。これは、特定の URL にアクセスするようにクライアントが誘導することで実現します。


GET /authorize?
response_type=code&
client_id=s6BhdRkqt3&
redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
 認証する URL の例
認証する URL のパラメータ一覧
属性必須/任意説明
response_type必須レスポンスとして要求するもの (アクセストークン、認可コード) を設定します。アクセストークンを要求する場合は "token" を設定します。認可コードを要求する場合は "code" を設定します。両方を要求する場合は "code_and_token" を指定します。
client_id必須クライアント識別子として、ユーザ名を設定します。
redirect_uri条件付き必須クライアントと認可サーバ間で事前にリダイレクト先の URI が決められていない場合は必須になります。エンドユーザによる認可ステップ完了時に、認可サーバがユーザーエージェントをリダイレクトする先の絶対 URI を設定します。
scope任意リクエストで要求するアクセス権のスコープを、空白区切りの文字列で指定します。scope の値に何を指定するかは認可サーバによって定義されます。空白区切りで複数の値を含む場合は、その順序に意味はありません。それぞれの値を合わせた範囲が要求するアクセス権になります。
state任意リクエストから完了時のコールバックまでの間、クライアントが状態管理できる値を指定します。認可サーバは指定された値をそのままリダイレクト URI に含めて、ユーザーエージェントをリダイレクトします。

上記の URL で指定されている redirect_uri は、ユーザがクライアントの認可後にリダイレクトされる URL になります。通常は、認証完了画面を指定しますが、iOS アプリケーションのカスタムスキーマを指定することで、クライアントアプリケーションに誘導することも可能です。例えば、以下は、Facebook から Twitter を連携する認証画面です。多くのクライアントでは画面上から認証が行われるため、HTTP リクエストを意識する必要はありません。


Facebook から Twitter を連携する認証画面
Facebook から Twitter を連携する認証画面


サービスプロバイダ側でログアウトしている場合は、クライアント側の連携する認証画面でクレデンシャルを求められます。これは、リソースオーナーとクライアントの間で高度な信用があるときのみ利用するべきで、クレデンシャルの入力は非推奨です。サービスプロバイダ側でログインしている場合は、クレデンシャルを求められませんので、連携を始める前にサービスプロバイダ側でログインを済ませておきましょう。

認可レスポンス

エンドユーザがアクセス要求を許可した場合、認可サーバはアクセストークン、認可コード、またはその両方を発行し、以下のパラメーターを付加した上でリダイレクト URI へリダイレクトさせます。

認可レスポンスのパラメータ一覧
属性必須/任意説明
code条件付き必須レスポンスタイプが "code" または "code_and_token" の場合は必須になります。その他の場合は省略されます。
access_token条件付き必須レスポンスタイプが "token" または "code_and_token" の場合は必須になります。その他の場合は省略されます。
expires_in条件付き必須アクセストークンが含まれていた場合は必須になります。アクセストークンの生存期間が秒単位で表されます。例えば、3600 という値は認可サーバによって、レスポンスが生成された 1 時間後にアクセストークンが失効することを意味します。
scope任意アクセストークンが含まれていた場合に、アクセストークンのスコープを、空白区切りの文字列で表します。scope の値に何を指定するかは認可サーバによって定義されます。空白区切りで複数の値を含む場合は、その順序に意味はありません。それぞれの値を合わせたアクセス範囲が要求するアクセス権になります。
state条件付き必須クライアントの認可リクエストに state パラメータが含まれていた場合は必須になります。クライアントから受け取った値をそのままセットします。

認可サーバがリダイレクト URI に追加されるパラメータは、クライアントからの認可リクエストに含まれる response_type パラメータによって指定されるレスポンスタイプで決まります。


レスポンスタイプが code の場合:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?
code=i1WsRn1uB1
 レスポンスタイプが code の場合のレスポンス例

レスポンスタイプが token の場合:

HTTP/1.1 302 Found
Location: http://example.com/rd?
access_token=FJQbwq9&
expires_in=3600
 レスポンスタイプが token の場合のレスポンス例

レスポンスタイプが code_and_token の場合:

HTTP/1.1 302 Found
Location: http://example.com/rd?
code=i1WsRn1uB1&
access_token=FJQbwq9&
expires_in=3600
 レスポンスタイプが code_and_token の場合のレスポンス例

エラーレスポンス

エンドユーザがアクセス要求を拒否、または要求が不正だった場合、認可サーバはリダイレクト URI のクエリー部に、以下に示すパラメーターを追加して、クライアントにエラーを通知します。


HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access-denied
 認可エラーレスポンスの例
エラーレスポンス一覧
属性必須/任意説明
error必須以下のいずれかの 1 つのエラーコード
  • invalid_request
    リクエストが必須のパラメーターを含んでいない。サポートしていないパラメーター、またはパラメーター値を含んでいる、あるいは形式に問題がある。
  • invalid_client
    指定されたクライアント識別子が不正。
  • unauthorized_client
    クライアントは指定されたレスポンスタイプを使用する権限を持たない。
  • redirect_uri_mismatch
    指定されたリダイレクト URI が事前登録されたものとマッチしない。
  • access_denied
    エンドユーザか、認可サーバが要求を拒否した。
  • unsupported_response_type
    要求されたレスポンスタイプを認可サーバがサポートしていない。
  • invalid_scope
    要求されたスコープが不正または、未知、あるいは形式に問題がある。
error_description任意エラーの概要。エラーについての詳しい情報を人間に読める形式で記したもので、発生したエラーを理解・解決するために使用される。
error_uri任意エラーについての情報を記した Web ページを指す URI 。エンドユーザにエラーについての詳しい情報を提供するために使用される。
state条件付き必須クライアントの認可リクエストに state パラメーターが含まれていた場合は必須。クライアントから受け取った値をそのままセットする。

アクセストークンの取得

"認可コード" と "クライアントの個別の認証情報" を利用して、アクセストークンの取得を行います。アクセストークンの取得は以下のようなリクエストで行います。


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
 アクセストークンを取得するリクエスト例
アクセストークン取得リクエストのパラメータ一覧
属性必須/任意説明
grant_type必須リクエストに含まれるアクセス許可タイプを設定します。
値は以下のいずれかになります。
  • authorization_code
  • password
  • assertion
  • refresh_token
  • none
client_id条件付き必須クライアントアイデンティティが (アサーションなどの) 他の方法で確立されない限り必須になります。クライアント識別子として、ユーザ名を設定します。
scope任意要求するアクセス範囲を空白区切りの文字列で設定します。scope の値に何を指定するかは認可サーバによって定義されます。空白区切りで複数の値を含む場合は、その順序に意味はありません。それぞれの値を合わせたアクセス範囲が要求するアクセス権になります。

アクセストークン取得レスポンス

認可コード "code" とクライアント個別の認証情報をサービスプロバイダに送り、リクエストが正当であればアクセストークンを獲得できます。以下は、アクセストークンの獲得が成功したレスポンス例です。


HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token":"SlAV32hkKG",
  "expires_in":3600,
  "refresh_token":"8xLOxBtZp8"
}
 アクセストークンを獲得したリクエスト例
アクセストークン取得レスポンスのパラメータ一覧
属性必須/任意説明
code条件付き必須レスポンスタイプが "code" または "code_and_token" の場合は必須になります。その他の場合は省略されます。
access_token条件付き必須レスポンスタイプが "token" または "code_and_token" の場合は必須になります。その他の場合は省略されます。
expires_in条件付き必須アクセストークンが含まれていた場合は必須になります。アクセストークンの生存期間が秒単位で表されます。例えば、3600 という値は認可サーバによって、レスポンスが生成された 1 時間後にアクセストークンが失効することを意味します。
scope任意アクセストークンが含まれていた場合に、アクセストークンのスコープを、空白区切りの文字列で表します。scope の値に何を指定するかは認可サーバによって定義されます。空白区切りで複数の値を含む場合は、その順序に意味はありません。それぞれの値を合わせたアクセス範囲が要求するアクセス権になります。
state条件付き必須クライアントの認可リクエストに state パラメータが含まれていた場合は必須になります。クライアントから受け取った値をそのままセットします。

エラーレスポンス

トークンリクエストが不正または未認可の場合、認可サーバーはメディアタイプ application/json で HTTP レスポンスボディに以下に示すパラメーターを付与して、クライアントにエラーを通知します。


HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store

{
  "error":"invalid_request"
}
 認可エラーレスポンスの例
エラーレスポンス一覧
属性必須/任意説明
error必須以下のいずれかの 1 つのエラーコード
  • invalid_request
    必須のパラメーターを指定していない、サポートされていないパラメーター、もしくは値を含んでいる、パラメーターに重複がある、複数クレデンシャルを含む、クライアント認証の為の 2 つ以上のメソッドを利用している、あるいは形式に問題がある。
  • invalid_client
    不正なクライアント識別子を指定した、クライアントが認証に失敗した、クライアントがクレデンシャルを含めなかった、複数のクライアントクレデンシャルを指定した、サポートされていないクレデンシャルタイプを使用した。
  • unauthorized_client
    クライアントは指定されたアクセス許可タイプを使用する権限を持たない。
  • invalid_grant
    指定されたアクセス認可が無効、期限切れ、もしくは取り消された。(例えば、無効なアサーション、期限切れの認可コード、間違ったエンドユーザーのパスワードクレデンシャル、認可コードとリダイレクト URI の不一致など)
  • unsupported_grant_type
    リクエストに含むアクセス許可のタイプもしくは他の属性が認可サーバでサポートされていない。
  • invalid_scope
    要求されたスコープが不正または、未知、あるいは形式に問題がある、もしくは前回許可した scope の範囲を超えている。
error_description任意エラーの概要。エラーについての詳しい情報を人間に読める形式で記したもので、発生したエラーを理解・解決するために使用される。
error_uri任意エラーについての情報を記した Web ページを指す URI 。エンドユーザにエラーについての詳しい情報を提供するために使用される。

アクセストークンの生存期間とリフレッシュトークン

アクセストークンは認可範囲によっては重要な情報の参照・変更ができるため、永続的に使い続けることはセキュリティ上、安全ではありません。そのため、アクセストークンには生存期間が設けられており、30 ~ 60 分程度と、比較的短い時間に設定されています。


しかし、生存期間を経過したら再びクレデンシャルを要求するのはユーザビリティが低下するため、これを避けるためにリフレッシュトークンを利用します。リフレッシュトークンは、アクセストークンを再取得するためのトークンで、クレデンシャルを必要とせずにアクセストークンを獲得できます。


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
 アクセストークンを獲得したリクエスト例

まとめ

OAuth は、従来のクレデンシャルによってすべての情報を参照・変更するという考え方から、認可された限定的な範囲で情報を扱える方法です。仕組みと認証方法を見ると難しく感じるかもしれませんが、一般的なサービスプロバイダではボタンをクリックするだけで簡単に終了します。しかし、OAuth を利用して API を作る場合は、認可コードやアクセストークン、リフレッシュトークンは重要な概念となるため是非覚えておきましょう。