Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
OAuth 2.0&OpenID Connectユースケースと関連仕様まとめ

OAuth 2.0&OpenID Connectユースケースと関連仕様まとめ

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

2017年7月21日

仕様策定から5年がたったOAuth 2.0の現状と概要を紹介。「Webアプリ」「ネイティブアプリ」「JavaScriptアプリ」といったOAuth 2.0の各種ユースケースについて、仕様を読み解きながら説明する。

OpenID Foundation Japan Nov
  • このエントリーをはてなブックマークに追加

 はじめまして、OpenID Foundation Japan事務局長のNovです。

 このたびは、Build InsiderでOAuth 2.0とOpenID Connectに関する記事を書かせていただくことになりました。

 今回はOAuth 2.0、次回はOpenID Connectについて、ユースケースごとのフロー(Flow)や関連仕様についてまとめていきます。

OAuth 2.0仕様策定から5年

 OAuth 2.0はIETF OAuth WG*1で仕様策定されている標準仕様群である。

  • *1 WGとは、ワーキンググループ(作業部会)のこと。

 最もコアとなるRFC 6749&RFC 6750はどちらも2012年にRFC化されており、すでに策定から5年以上が経過している。OpenID Foundation Japanの翻訳WGでもこれらは翻訳済みである。

 2017年現在では、OAuth 1.0を採用しているTwitter APIを除けば、以下に挙げるような多くのAPIでOAuth 2.0が採用されている。

 また上記のように広くオープンなAPIだけでなく、特定のサービスが内部的に利用しているAPIにおいても、OAuth 2.0ベースの仕組みを利用しているサービスは数多く観測されている。

 一方で、前述のRFC 6749は「あらゆるユースケースを想定して」書かれたドキュメントであることから、特定のユースケースでOAuth 2.0を利用したいといった場合には「どこを読むべきか」からして分からないという意見も多い。

 そこで本記事では、代表的なユースケースごとにRFC 6749翻訳版の該当セクションなどにリンクしながら、ユースケースごとのOAuth 2.0の仕様の読み方を整理していく。

OAuth 2.0概要

 OAuth 2.0には、図1に示すような4つの登場人物が存在する(「RFC 6749 - Section 1.1」参照)

図1. OAuth Roles

 Resource Ownerはエンドユーザーのことであり、特に説明はいらないだろう。

 OAuth Clientとは、APIプロバイダー(提供者)が提供するAPIにアクセスすることで何らかのサービスを提供する主体である。

 Authorization ServerResource Serverは多くの場合、同じ事業者によって運営されており、かつ同一サーバー内に共存していることも多いが、Authorization Serverはエンドユーザーの認証・トークンの発行などを担当し、Resource ServerはAuthorization Serverが発行したトークンを受け入れて実際のAPIを提供するサーバーである。

 図1では、Google Calendar APIを利用してカレンダー機能を提供するiOSカレンダーアプリの例を示している。

 これらの登場人物のうち、Authorization ServerおよびResource Serverはほとんどの場合、Webサーバー上で動作するサービスであり、Resource Ownerはほとんどの場合、人間である。

 よって、OAuth 2.0におけるユースケースの多様性は、その多くの部分がOAuth Clientの多様性から発生している。

OAuth 2.0ユースケース一覧

 本記事で対象とするユースケースは、APIを利用する側のサービス(OAuth Client)の特性ごとに以下のようになる。なお「JSアプリ」とは「JavaScriptによりブラウザー上で動作するクライアントアプリ」のことで、サーバーサイドで動作する「Webアプリ」とは別のタイプのクライアントである。

  • Webアプリ:
    • secret(=クライアントIDに対応するパスワードなどのクレデンシャル情報)を秘匿に保てる
    • (バッチ処理など)比較的長期間有効なAPIアクセス権限を必要とする場合がある
  • ネイティブアプリ(アプリ独自のBackend Serverなし):
    • secretを秘匿に保てない
    • 初回起動時以外はログインさせることもないので、長期間有効なAPIアクセス権限を必要とする
    • 端末は特定ユーザーと1対1でひも付くことが想定されるため、ユーザー認証の重要性は低い
  • ネイティブアプリ(アプリ独自のBackend Serverあり):
    • secretを秘匿に保てない
    • 初回起動時以外はログインさせることもないので、長期間有効なAPIアクセス権限を必要とする
    • バックエンド側では全ユーザーのデータを管理しているため、バックグラウンドではユーザー認証が必要
  • JSアプリ(アプリ独自のBackend Serverなし):
    • secretを秘匿に保てない
    • 常にユーザーがブラウザーの前にいることが期待できるため、必要に応じてAPIアクセス権限を再取得可能
    • ブラウザーは特定ユーザーと1対1でひも付くことが想定されるため、ユーザー認証の重要性は低い
  • JSアプリ(アプリ独自のBackend Serverあり):
    • secretを秘匿に保てない
    • 常にユーザーがブラウザーの前にいることが期待できるため、必要に応じてAPIアクセス権限を再取得可能
    • バックエンド側では全ユーザーのデータを管理しているため、バックグラウンドではユーザー認証が必要

 「RFC 6749 - Section 2」では、上記のようなOAuth Clientのタイプやクライアント認証の可否について述べられている。

 それぞれの内容を、仕様書に照らし合わせながらより詳しく見ていこう。

Webアプリ向けのOAuth 2.0利用パターン

 これは最も基本となるOAuth 2.0のユースケースであり、OAuth対応しているAPIではほぼ100%サポートされている。

 「RFC 6749 - Section 4.1」に述べられているAuthorization Code(認可コード)フローが、このユースケースで用いられるOAuthフローである。

 このフローをシーケンスにすると図2のようになる。

図2. OAuth Sequence for Web App

 ここではOAuth ClientはWebサーバー上で動作するサービスであり、よほどのこと(サーバーへの不正侵入など)がない限りsecretを漏えいさせる心配はなく、Client Secretと呼ばれる鍵を持つことができる。このようなクライアントがConfidential Clientと呼ばれるのは、「RFC 6749 - Section 2」に記載されている通りである(ちなみに、後述のネイティブアプリやJSアプリのようにsecretを秘匿に保てないタイプはPublic Clientと呼ばれる)。

 Client Secret(client_secretパラメーター)が保持できることで、Authorization Serverとの間でクライアント認証が可能となるため、クライアント認証が利用できない後述のケースよりも高いセキュリティレベルが期待できる。

 APIプロバイダーによっては、このクライアント認証の有無によって、発行されるAccess Token(アクセストークン)の有効期限やRefresh Token(リフレッシュトークン)の発行可否、利用できるAPI Permission(scopeリクエストパラメーター)などに差を付けるといったことも多い。

 上記シーケンスの“Authorization Request(認可リクエスト)”、“Authorization Response(認可レスポンス)”、“Token Request(トークンリクエスト)”、“Token Response(トークンレスポンス)”での各種パラメーター等については、それぞれ「RFC 6749 - Section 4.1.14.1.24.1.34.1.4」を参照のこと。

 なおRFC 6749ではstateリクエストパラメーターはOPTIONAL(任意)となっているが、stateを用いない場合Authorization Responseを受け取る箇所でのCSRF攻撃が可能となる。CSRF攻撃が成立した場合の被害の有無はOAuth Clientのサービス内容などにも依存するが、基本的には常にブラウザーセッションとひも付けられたstateを利用してCSRF対策を行うことが望ましい。Authorization Response時のCSRF攻撃およびstateパラメーターの利用については、OAuth 2.0のセキュリティモデルについてまとめた「RFC 6819のSection 3.64.4.1.85.3.5」に詳しい。

 またredirect_uriリクエストパラメーターを事前登録済みのものとの部分一致で検証するAPIプロバイダーも存在するが、これからAPIを提供する場合には完全一致のみを許容することが望ましい。redirect_uriの部分一致を許容すると、OAuth Client側に(意図せず)存在するオープンリダイレクター(Open Redirector)を悪用され、Authorization Codeが漏えいするなどの被害が発生するリスクが高まる。OAuth Client側の立場では、redirect_uriの検証方式はコントロールできないが、Authorization Serverのredirect_uri検証方式次第では他の箇所のオープンリダイレクターが利用されてAuthorization Codeが漏えいしてしまう可能性があることを知っておくこと。redirect_uriの検証が部分一致だった場合のリスクなどについては、筆者が以前「OAuth 2.0の脆弱性 (!?) "Covert Redirect" とは - OAuth.jp」というブログ記事にまとめているので、そちらを参照のこと。

ネイティブアプリ(独自Backend Serverなし)

 Authorization Server&Resource Serverとは別の独自のバックエンドサーバー(以下、Backend Server)を持たないタイプのネイティブアプリのケース。独自のBackend ServerがAuthorization Server&Resource Serverと同一である場合も含む。

 このケースでは、取得したAccess Tokenを使ってAPIアクセスを行うのはネイティブアプリ自身のみである。

 ネイティブアプリについては、現状のiOS/Androidプラットフォーム上では、各デバイスごとに個別の鍵を埋め込むことは不可能であり、そのため1端末から鍵が漏えいすると、当該アプリをインストールしている全端末が影響を受け得る。そのためネイティブアプリにClient Secretを埋め込むことは推奨されておらず、埋め込んだとしてもそれをクライアント認証目的で利用することは禁じられている(「RFC 6749 - Section 2.3」参照)

 このケースに対応するOAuthのフローは大きく分けて2通り存在する。

Implicitフローを利用するケース

 このフローはRFC 6749策定当初からネイティブアプリ(およびJSアプリ)向けに想定されていたものであり、シーケンスにすると図3のようになる。

図3. OAuth Sequence for Native App w/o Backend

 クライアント認証がそもそも不可能であることから、「RFC 6749 - Section 4.2」にあるImplicit(インプリシット)フロー(=クライアント認証を行わないフロー)を利用しよう、というのが大本のコンセプトである。

 このフローでは、前述のWebアプリ向けのフローと異なりAuthorization Codeは利用せず、Authorization Responseで直接Access Tokenが返される。そのため図2にはあったToken Request&Token Responseも不要である。

 上記シーケンスの“Authorization Request”や“Authorization Response”での各種パラメーター等については、それぞれ「RFC 6749 - Section 4.2.14.2.2」を参照のこと。

 クライアント認証がないため、クライアントの身元を保証する手段はredirect_uriしかない。redirect_uriが他のアプリに乗っ取られてしまうようなケースでは、Access Tokenは容易に第三者に漏えいする。よって第三者のアプリに利用されてしまうリスクをはらんでいるiOS/AndroidのCustom Schema URLの利用にはリスクを伴う。

 RFC 6749策定当初は、iOS Universal LinksAndroid App Linksなど、特定HTTPS URLを特定アプリにひも付ける仕組みがなかったことから、かつてはCustom Schema URLを利用するアプリが多く見受けられたが、今後は上記のようなHTTPS URLをredirect_uriとして利用することが推奨される。

 なお、このケースも含め、以降の全てのケースにおいて、stateの利用とredirect_uriの完全一致による検証は強く推奨される(詳しくは前節参照)

Authorization Codeフローを利用するケース

 ネイティブアプリは「一度ログインしてしまえばそれ以降はずっとログイン状態」というのが一般的なUX(ユーザーエクスペリエンス)である。よって継続的にAPIアクセスが必要なアプリの場合、長期間有効なAccess Tokenが必要となる。

 しかしながらクライアント認証が行えないImplicitフローでは、Access Tokenの有効期限は15分~1時間程度に抑えることが望ましい。

 この矛盾を解決するため、ネイティブアプリに対しても(前掲の)図2と同様のAuthorization Codeを利用したフローを採用するケースもまま見受けられる。Authorization Codeフローを利用すると、Refresh Tokenという第2のトークンを利用でき、それを利用するとユーザーインタラクションなしに適宜新たなAccess Tokenを発行できる。

 なお、Authorization Codeフローを利用してもネイティブアプリがクライアント認証を行えないという特徴は変わらないため、クライアント認証なしのAuthorization Codeフローを利用することとなる。このケースを扱えるようにするため、「RFC 6749 - Section 4.1.3」ではClient Secretが発行されていないクライアントにはクライアント認証を要求しない、という書き方になっている。

ネイティブアプリ(独自Backend Serverあり)

 Authorization Server&Resource Serverとは別に、独自のBackend Serverにアクセスするタイプのネイティブアプリはこのケースとなる。

 ユーザーによるログイン行為を必要とするネイティブアプリは、本質的に「自身のBackend Server上にある各ユーザーのデータのうち当該ユーザーにひも付くデータをネイティブアプリに渡す」といった処理が必ず入るため、このケースに該当する。Facebook IDでログインするアプリなど、外部IDログインをサポートするアプリはその典型例である。

 このケースにおいてまず知らなければならないことは、「このケースを前提としたRFCは存在しない」という事実である。

 このケースは図1で示したOAuth 2.0が前提としている登場人物に加え、OAuth ClientのBackend Serverという第5の登場人物を含んでおり、OAuth 2.0の扱う前提を超えてしまっているのである。

図4. OAuth Roles w/ Client Backend

 このケースは多くのネイティブアプリに当てはまる割に標準的な対応方法が明文化されていないことから、以下に挙げるようにいくつかの実装パターンが存在する(これ以外にもあり得るが、紙面の都合上その他のパターンは割愛する)

図5.1. Implicitフロー+Access TokenをBackend Serverに送るパターン

 図5.1のように、ネイティブアプリが受け取ったAccess TokenをBackend Server(OAuth Client Backend)に送り、Backend ServerからAPIアクセスを行うケースを考えてみよう。第三者のアプリが当該Backend Serverのエンドポイントに、第三者アプリ向けに発行されたAccess Tokenを投げつけた場合でも、そのAccess Tokenを利用してResource Serverから取得したプロファイルデータ(Profile Data)にひも付くユーザーがBackend Serverに存在すれば、当該ユーザーにひも付く当該クライアント上のデータが返されてしまう。この性質を利用した攻撃をToken置換攻撃という。この攻撃については筆者のブログ記事を参照のこと。

 Token置換攻撃への対策のため、FacebookはDebug Token APIという専用APIを提供している。このAPIを利用することで、当該Access Tokenがどのクライアントに対して発行されたものかを知ることができ、第三者アプリ向けに発行されたAccess Tokenの受け入れを拒否できるようになる。

 ただしToken置換攻撃の根本原因は、OAuth 2.0が本来ID連携を目的に策定されたものでないという点にある。ID連携を目的としてOAuth 2.0を使う場合、ID連携のためにOAuth 2.0にかけていた部分を拡張として定義したOpenID Connectを利用することが望ましい。GoogleやMicrosoftなど、OpenID Connectが利用可能なAPI Platformでは、素のOAuth 2.0ではなくOpenID Connectを利用しよう。OpenID Connectに関しては、次回の記事で紹介する。

図5.2. Hybridフロー+Authorization CodeをBackend Serverに送るパターン

 図5.2のケースでは、Authorization ResponseとしてAccess TokenとAuthorization Codeを同時に受け取ることになる。このフローはAuthorizationエンドポイントとTokenエンドポイント双方からAccess Tokenが発行されることになることから、Hybrid(ハイブリッド)フローと呼ばれる。なおHybridフローはRFC 6749では定義されておらず、OpenID Connectの策定段階においてOAuth 2.0の拡張仕様として出てきた「OAuth 2.0 Multiple Response Type Encoding Practices」(英語)というドキュメントに定義されている。ドキュメントにある仕様のresponse_type=code%20token(=response_typeパラメーターの値がcode tokenの場合)が図5.2に相当する。

 このケースでは、Backend Serverに送られたAuthorization Codeは、Backend ServerとAuthorization Serverの間のクライアント認証を経てAccess Tokenと交換されるため、第三者のアプリに発行されたAuthorization Codeと置換されたとしてもクライアント認証時にエラーとなる。よってこのケースでは、Token置換攻撃のような攻撃パターンは意識する必要がない。

 HybridフローはRFC 6749では未定義のため、全てのAPIプラットフォームで利用できるわけではないが、Facebook、GoogleはじめHybridフローをサポートするプラットフォームも増えてきていることから、可能な限り図5.1のフローではなく図5.2のフローの利用を推奨する。

JSアプリ(独自Backend Serverなし)

 JSアプリはClient Secretを秘匿に保てないという点ではネイティブアプリと同等である。

 しかしながら、JSアプリは(オフラインで動作するような実装になっていない限り)常にブラウザーの前にユーザーがいることが期待でき、任意のタイミングでユーザーをAuthorization Serverに送って新たなAccess Token発行を依頼することができるため、ネイティブアプリのように「一度ログインしたらそれ以降はずっと(Authorization Serverにユーザーを送ることなく)ログイン状態を維持しなければならない」といった要件は持ち合わせない。

 よって前述のImplicitフローを利用すればよい。

 JSアプリはその性質上、redirect_uriリクエストパラメーターとしてはHTTPS URLを利用可能なので、ネイティブアプリでのCustom Scheme URLに関する注意点などは気にする必要がない。

 Access Tokenの有効期限が切れてしまい新たなAccess Tokenが必要になった場合には、Refresh Tokenを用いて新たなAccess Tokenを取得するのではなく、Authorization ServerにAuthorization Requestを送信し直す(=ユーザーをAuthorization Serverにリダイレクトさせる)ことになる。

 このリダイレクトがUXを損ねるという場合には、hidden iframe(=visibility: hiddenスタイルを指定した<iframe>要素)内でAuthorization Requestを送信し、当該<iframe>経由で新たなAccess Tokenを取得することも検討に値する。APIプラットフォームによっては、ユーザーが認証済みかつ当該クライアントによるAPIリクエストを許諾している場合には、Authorization Requestに対してユーザーインタラクションなしで即座にAccess Tokenを発行するものもあり、そういったプラットフォームではこのhidden iframeを用いた手法が利用できる。

JSアプリ(独自Backend Serverあり)

 このケースも独自Backend Serverありのネイティブアプリとほぼ同様のフローになる。

 図5.1のフローを利用する場合は、くれぐれもToken置換攻撃対策を怠らないこと。可能な限り図5.2のフローを推奨する。

 またネイティブアプリと異なる点として、Access Tokenの再発行については前節のhidden iframeを利用する方法が利用可能である。

まとめ

 ここまでで5つの代表的なユースケースについて、OAuthの利用方法をまとめた。

 ネイティブアプリにおけるCustom Scheme URL利用時の注意点や、独自Backend ServerでAccess Tokenを直接受け取る場合のリスクなど、見逃すと即脆弱性につながるものも多いことから、自身が利用しているパターンごとの特徴を押さえておこう。

 またAPIプロバイダー側はredirect_uriリクエストパラメーターの検証方法や想定するOAuth Clientのタイプに合わせたフローのサポートなど、Authorization Server設計時の参考にしてほしい。

 次回はOAuth 2.0をベースに、ID連携(ソーシャルログイン、SSO、ID Federationなどと呼ばれることもある)に必要な機能群を標準化したOpenID Connectについて紹介する。OpenID Connectは「OAuth 2.0にどのような機能を追加すればセキュアなID連携が可能となるのか」という視点で策定された仕様であるため、現状OAuth単体でID連携を行っているデベロッパーの方にもぜひ学んでほしいプロトコルである。

OAuth 2.0&OpenID Connectユースケースと関連仕様まとめ
1. 【現在、表示中】≫ OAuth 2.0の代表的な利用パターンを仕様から理解しよう

仕様策定から5年がたったOAuth 2.0の現状と概要を紹介。「Webアプリ」「ネイティブアプリ」「JavaScriptアプリ」といったOAuth 2.0の各種ユースケースについて、仕様を読み解きながら説明する。

OAuth 2.0&OpenID Connectユースケースと関連仕様まとめ
2. OpenID Connectユースケース、OAuth 2.0の違い・共通点まとめ

OpenID ConnectとOAuth 2.0は何が違い、何が共通するのかを概説。OpenID Connectの主要なユースケースについて、Clientタイプ別と認証パターン別に説明する。

サイトからのお知らせ

Twitterでつぶやこう!