OAuth 2.0 の仕組みと認証方法

OAuth 2.0 とは

quote
映画「フェリスはある朝突然に」では、駐車場係がフル整備された 1961 年もののフェラーリを勝手に乗り回すシーンが登場します。あなたの新車のマスタングが同じ目に遭わないようにするにはどうすればよいでしょうか。最近の車には、駐車場係に認める操作を制限して、トランクを開けたりスピードを出して運転できないようにした特殊な鍵が付いていることがあります。OAuth が生まれたのは、オンラインにおいて、これと本質的に同じ問題を解決するためです。
OAuth の誕生
(参考:Ryan Boyd(著)「OAuth 2.0 をはじめよう」)

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

そこで新しく考えだされた方法として、すべての情報にアクセスするのではなく部分的な情報にだけアクセスを許可する OAuth という仕様です。OAuth 2.0 は RFC6749 として定義されており、詳細な仕様は以下から確認することができます。

クレデンシャル情報による認可を使用しない理由はいくつかあります。一番重要な問題は、信頼性の問題です。クレデンシャル情報を渡すことは、すべての情報に対するアクセス権限を委譲することになります。例えば、悪意のあるアプリケーションが、ユーザに対してクレデンシャル情報を要求した場合、ユーザの個人情報が漏洩する危険があります。しかし、OAuth を使うと、異なるサービスと連携するためユーザにクレデンシャル情報を譲り渡すという要求をしなくても、ユーザの個人情報へセキュアにアクセスできるようになります。

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

つまり、あなたが許可した人に、すべての機能を解錠できるマスターキーではなく、限定的な機能にしかアクセスできない特殊な鍵を渡すイメージになります。これは、冒頭で紹介した映画の例と本質的にまったく同じことです。

用語定義

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

OAuth における用語定義
用語説明
保護リソースリソースサーバ内にあるアクセスが制限されたリソースです。例えば、ツイッターのサーバ内にある情報 (リソース) はクレデンシャル情報によって保護されています。このような保護リソースには、OAuth 認証済みリクエストを利用することでクレデンシャル情報を必要とせずに取得が可能になります。
リソースサーバ保護リソースに対するリクエストを受付け、レスポンスを返すサーバです。例えば、Facebook から ツイッターのサーバに個人情報をリクエストする場合、ツイッター側がリソースサーバになります。
クライアントリソースサーバにアクセスするサービス、またはアプリケーションです。例えば、Facebook から ツイッターのサーバにリクエストする場合、Facebook 側がクライアントになります。クライアントは、エンドユーザからの認可を取得して、保護リソースに対してリクエストを行います。
リソースオーナー保護リソースの持ち主で、多くの場合はエンドユーザと同じ意味です。保護リソースへのアクセスを認可するエンティティとなります。
エンドユーザリソースを所有するユーザで、多くの場合はリソースオーナーと同じ意味です。リソースオーナーはエンティティである点に対して、エンドユーザは何らかの操作 (認可ボタンを押下するなど) が必要になるタイミングで登場します。
トークンクライアントに対して発行されたアクセス認可を表す文字列です。クライアントとリソースサーバ間で、不正なリクエストではないことを示すための一種の鍵情報であり、トークン自体は乱数で表されます。またトークンの付加情報として、リソースオーナーによって許可された権限の範囲やアクセス期間も含まれています。
アクセストークンクライアントがリソースオーナーの代わりに認証済みリクエストを行うために使われるトークンです。アクセストークンを利用することでクレデンシャル情報を使わず、保護リソースへ部分的にアクセスできます。
リフレッシュトークンクライアントがリソースオーナーの新しいアクセストークンを得るために使われるトークンです。アクセストークンを永続的に利用可能にしておくことはセキュリティ上のリスクがあります。そのため、アクセストークンには利用期限が設けられています。利用期限が切れたアクセストークンを再び取得するためにリフレッシュトークンを使うことで、エンドユーザの操作を省略することができます。
認証
(Authentication)
認証とは、ユーザが「自分自身を何者であると主張しているか」を検証するプロセスです。現実世界では、警官が身分証の提示を求めるとき、警官は身分証の写真とあなたの外観を見比べて本人確認を行います。インターネット上における、認証とはあなたがアカウント所有者であることを確認する作業になります。通常の認証プロセスでは、ユーザ名とパスワードの入力が求められます。
認可
(Authorization)
認可とは、何らかの行為 (ドキュメントを読んだり、電子メールアカウントにアクセスするなど) を行う際に、当該ユーザにその権限があるかどうかを検証するプロセスです。通常、現在のユーザに対する権限の有無を確認するには、最初にユーザのアイデンティティを検証 (認証) しなければいけません。現実世界では、警官がスピード違反の車を止めたとき、まず最初に免許証を使って認証を行います。次に、あなたが運転を認可されているかどうか (期限切れでないか、限定事項がないかなど) を確認します。インターネット上における認可とは、認証後に各操作に対するアクセスが、許可されている範囲のデータとサービスに対するものであることを確認します。
認可コードOAuth 2.0 の認証フローに登場する生存期間の短いトークンで、認可コードはアクセストークンとリフレッシュトークンを得るために使われます。認可コードは、エンドユーザがリソースサーバへのアクセスを認可することによって発行されます。
認可サーバエンドユーザの認可と、クライアントの認証が成功した後、トークンを発行するためのサーバです。サービスプロバイダ側に含まれるサーバですが、厳密にはリソースサーバとは区別されますが、後述するフロー図では、リソースサーバに認可サーバも含めています。
認可エンドポイント認可サーバの HTTP エンドポイントで、エンドユーザの認証と認可の取得を行います。エンドポイントとは、それらの処理を行う終端のことを指します。
トークンエンドポイント認可サーバの HTTP エンドポイントで、トークンの発行とリフレッシュを行います。エンドポイントとは、それらの処理を行う終端のことを指します。
クライアント識別子認可サーバがクライアント自身を識別するために発行される一意な識別子です。クライアント識別子は client_id で表され、中身の情報は乱数で表現されます。
アサーション異なるトラストフレームワークを利用して取得したアクセス許可。アサーションは、クライアントがアクセストークンを取得するために既存のトラストリレーションシップを利用することを可能にします。それらは OAuth と他のトラストフレームワークのブリッジを提供します。
パスワードクレデンシャル直接リソースオーナーとクレデンシャルをやり取りをするときに使用されます。ただし、リソースオーナーパスワードクレデンシャルは、リソースオーナーとクライアントの間に高度な信用があるときのみ利用するべきです。

OAuth 1.0 と OAuth 2.0 の仕組み

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

OAuth 1.0 の認証フロー1OAuth 1.0 の認証フロー2
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 の認証フロー1OAuth 2.0 の認証フロー2
OAuth 2.0 の認証フロー

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

クライアントは、OAuth 認証を行うためにエンドユーザ (リソースオーナー) に対して認可を要求します。認可を行う場合、専用の画面で行う必要があるため、クライアントはアクセス許可を発行する認可サーバにリソースオーナーをリダイレクトさせます。

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

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

クライアントは保護リソースへのリクエストを実行し、アクセスを得るためにアクセストークンを提供します。リソースサーバはアクセストークンの正当性を確認し、有効な場合はリクエストを処理します。

クライアント認証 - リクエスト

本章は、上記フローの「A」に相当します。

ユーザがクライアントから他のサービスプロバイダに対して認証を求める場合、ブラウザには認可を求める画面が表示されます。例えば、Facebook から ツイッターに連携する場合、ツイッターからは以下のような認可を求める画面が表示されます。

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

※ 分解すると以下のようになります
client_id     = s6BhdRkqt3
scope         = https%3A%2F%2Fclient%2Eexample%2Ecom%2Fauth%2F
state         = i1WsRn1uB1
redirect_uri  = https%3A%2F%2Fclient%2Eexample%2Ecom%2F
response_type = code
クライアント認証リクエストの例
クライアント認証リクエストのパラメータ一覧
属性必須/任意説明
client_id必須クライアント ID。アプリケーションを登録した際に割り当てられた値を設定します。
scope任意リクエストで要求するアクセス権の種類をスペース区切りの文字列で指定します。scope として有効な値は認可サーバによって異なるため、API プロバイダのドキュメントを参照してください。
state任意 (推奨)アプリケーションに対する CSRF 攻撃を防ぐためにクライアントアプリケーションで使用する一意の値です。必須ではありませんが、設定することが推奨されます。この値は、リクエストごとに異なる乱数で一意な文字列である必要があります。
redirect_uri任意アプリケーションへのアクセス認可が終わった後にリダイレクトされる URI を指定します。通常、この値はあらかじめプロバイダに登録しておく必要があります。
response_type必須レスポンスの種類を表します。上記のフローはサーバサイドのフローであるため code が指定されます。code が指定された場合、認可リクエストを承認すると認可コードが返却されます。

クライアント認証 - レスポンス

本章は、上記フローの「B」に相当します。

HTTP/1.1 302 Found Location: https://client.example.com/?code=SplxlOBeZQQYbYS6WxSbIA&state=i1WsRn1uB1

※ 分解すると以下のようになります
code          = SplxlOBeZQQYbYS6WxSbIA
state         = i1WsRn1uB1
クライアント認証レスポンスの例
クライアント認証レスポンスのパラメータ一覧
属性必須/任意説明
code必須認可コードが発行されます。認可コードは漏洩のリスクを軽減するために、失効するまでの時間は最大でも 10 分になります。また、認可コードは使い捨てのコードであり、認証サーバは 2 回以上使用されているリクエストは拒否し、その認証コードに基づいて発行されたトークンは無効にする必要があります。
state条件付き必須リクエストに state が含まれている場合は必須になります。

クライアント認証 - エラーレスポンス

本章は、上記フローの「B」がエラーであった場合に相当します。

HTTP/1.1 302 Found Location: https://client.example.com/cb?error=access_denied&state=i1WsRn1uB1

※ 分解すると以下のようになります
error         = access_denied
state         = i1WsRn1uB1
クライアント認証エラーレスポンスの例
クライアント認証エラーレスポンスのパラメータ一覧
属性必須/任意説明
error必須error は、以下のいずれかの値を返します。
  • invalid_request
    リクエストに必須パラメータが欠落しているか、無効なパラメータ値が含まれている、パラメータに重複がある、またはその他の形式が誤っています。
  • unauthorized_client
    このメソッドを使用してクライアントに認証コードを要求する権限はありません。
  • access_denied
    リソースオーナー、または認可サーバが要求を拒否しました。
  • unsupported_response_type
    認可サーバは、response_typeで指定された認可コードの取得をサポートしていません。
  • invalid_scope
    リクエストで指定した scopeは無効、不明、または不正な形式です。
  • server_error
    認可サーバがが予期しないエラーを検出したため、リクエストを実行できませんでした。
  • temporarily_unavailable
    認可サーバは一時的な過負荷、または保守のためにリクエストを処理できません。
error_description任意エラーを理解するための追加情報が提供されます。
error_uri任意エラーに関する URI 情報が提供されます。
state条件付き必須リクエストに state が含まれている場合は必須になります。

アクセストークン取得 - リクエスト

本章は、上記フローの「C」に相当します。

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

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

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

※ 分解すると以下のようになります
grant_type    = authorization_code
code          = i1WsRn1uB1
redirect_uri  = https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
アクセストークン取得リクエストの例
アクセストークン取得リクエストのパラメータ一覧
属性必須/任意説明
grant_type必須値は authorization_code である必要があります。
code必須認可サーバから受け取った認可コードを設定します。
redirect_uri条件付き必須クライアント認証リクエストに redirect_uri が含まれている場合は必須です。その場合、クライアント認証リクエストに設定した redirect_uri と同じ値である必要があります。
client_id条件付き必須認可サーバによってクライアントが認証されていない場合は必須です。

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

本章は、上記フローの「D」に相当します。

アクセストークン取得リクエストが正当かつ、エンドユーザによって認可された場合、以下のようにアクセストークンの取得に成功します。リクエストが失敗、または不正であった場合は、エラーレスポンスを返します。

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}
アクセストークンを獲得したリクエスト例

アクセストークン取得 - エラーレスポンス

本章は、上記フローの「D」がエラーであった場合に相当します。

アクセストークン取得リクエストが失敗、または不正であった場合は、エラーレスポンスを返します。

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

{
  "error":"invalid_request"
}
認可エラーレスポンスの例
エラーレスポンス一覧
属性必須/任意説明
error必須error は、以下のいずれかの値を返します。
  • invalid_request
    リクエストに必要なパラメータが含まれていないか、サポートされていないパラメータが含まれている、パラメータに重複がある、複数クレデンシャルを含む、クライアント認証のための複数のメソッドを利用している、またはその他の形式が誤っています。
  • invalid_client
    クライアント認証に失敗しています。例えば、未知のクライアント、クライアント認証情報が含まれていない、サポートされない認証方式が利用されているなどです。
  • invalid_grant
    提供された認可グラント (認可コードやリソースオーナークレデンシャルなど)、リフレッシュトークンが不正・有効期限切れ・失効、認可リクエストで用いられたリダイレクト URI と不一致、または他のクライアントに対して発行されたものです。
  • unauthorized_client
    認証されたクライアントは指定されたアクセス許可タイプを使用する権限を持っていません。
  • unsupported_grant_type
    リクエストに含むアクセス許可のタイプもしくは他の属性が認可サーバでサポートされていません。
  • invalid_scope
    要求されたスコープが不正または、未知、あるいは形式に問題がある、もしくは前回許可した scope の範囲を超えています。
error_description任意エラーを理解するための追加情報が提供されます。
error_uri任意エラーに関する URI 情報が提供されます。

まとめ

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

Category:
プログラミング
公開日:
更新日:
Pageviews:
161,607
Shares:
237
Tag:
OAuth
HTTP
Server
Client
hatebu icon
hatebu