OAuth 2.0&OpenID Connectユースケースと関連仕様まとめ
OpenID Connectユースケース、OAuth 2.0の違い・共通点まとめ
OpenID ConnectとOAuth 2.0は何が違い、何が共通するのかを概説。OpenID Connectの主要なユースケースについて、Clientタイプ別と認証パターン別に説明する。
前回はOAuth 2.0を、主要ユースケースごとに、その特徴と注意点についてまとめた。
今回はOpenID Connectについて、同じように主要ユースケースごとにまとめていくが、まずはOpenID ConnectとOAuth 2.0の違い・共通点について見てみよう。
今回も前回同様、OpenID Foundation Japanが翻訳したOpenID Connect Core 1.0に適宜リンクしながら話を進める。
なおOpenID Connectでは、同じ対象に対してOAuth 2.0と異なる用語を用いることもあるが、本稿ではそれらの用語はなるべくOAuth 2.0のものを使うようにする。参考のために以下にOAuth 2.0とOpenID Connectの用語対応表を載せておくので、上記翻訳仕様や外部の記事などを読み進める際の参考にしてほしい。
OAuth 2.0での呼び名 | OpenID Connectでの呼び名 |
---|---|
Authorization Server | OpenID Provider(OP)/Identity Provider(IdP) |
Authorization Request | Authentication Request |
Authorization Response | Authentication Response |
Client | Relying Party(RP) |
OpenID Connect概要
OpenID Connectをひと言で説明すると、
OAuth 2.0 + Identity Layer = OpenID Connect
という表現が最もふさわしい。
OpenID Connectは、「OAuth 2.0を使ってID連携をする際に、OAuth 2.0では標準化されていない機能で、かつID連携には共通して必要となる機能を標準化した」OAuth 2.0の拡張仕様の一つである。
OpenID Connect登場以前は、OAuth 1.0/2.0ベースのID連携の仕組みがTwitterやFacebookなどの巨大SNSから提供され、人気を博した。これらの仕組みは今でも広く利用されている。
一方で、OpenID Connectの1つ前のバージョンのOpenID 2.0では、ID情報の連携はできるもののAPI連携には利用できないなど、デベロッパーに強く訴求できていない状況であった。
そのような状況を打開すべく、当時の最新OAuth仕様であったOAuth 2.0をベースにOpenIDを一から設計し直したものが、OpenID Connectである。
OAuth 2.0を使ったID連携の実例としては、OpenID Connect仕様策定時点においても参考とされたFacebook Loginが良い例であろう。
実際、OpenID Connectという名前は、当時のFacebook Loginのブランド名である“Facebook Connect”から来ており、OpenID Connectの仕様策定においてもFacebookの実装が強く影響を与えている。
そこで本稿では、まずFacebook Loginのフロー(Flow)をながめながら、ID連携に必要な機能をまとめることにする。
Facebook Loginから学ぶID連携に必要な機能
Webアプリ向けのFacebook Loginは、以下のようなフローになる。
Facebookの場合はAuthorization ServerとResource Serverが共通なので、前回の記事のWebアプリのケースと登場人物が少し異なるが、本質的には同様である。
Facebook Loginでは、OAuth ClientはAccess Token取得後にFacebook Graph APIのProfile API(GET /me
)から当該Access Tokenにひも付くFacebookユーザーのプロフィール情報を取得することで、そのFacebookユーザーにひも付くOAuth Client側のユーザーを特定し、OAuth Client側での認証を行う。
また新規ユーザーの場合には、取得したプロフィール情報(メールアドレスや氏名など)を使い、その場でユーザーを登録する(OAuth Client側でユーザーから直接追加のプロフィール情報を取得してから登録させる場合もある)。
Facebook Loginに限らず、OAuth 2.0ベースのID連携では、OAuth 2.0対応のProfile APIから取得されたAPIプロバイダー側のUser IDを基に、OAuth Client側でユーザーを認証することになる。
つまり、OAuth 2.0を使ったID連携では、以下の2点が必須要件となっている。
- APIプロバイダー側のUser IDが取得できること
- APIプロバイダー側のプロフィール情報が取得できること
また前回の記事で紹介したToken置換攻撃が起こり得るケースでは、Access Token発行先のClientを特定するためのAPI(e.g. Facebook Debug Token API)も必要になる。
そして最も重要なことは、「OAuth 2.0はUser IDの取得方法やProfile APIの仕様については定めていない」ということである。OpenID Connectでは、これらを標準化しているのである。
Identity Layer = ID Token + UserInfo API
OpenID Connectでは、前述の要件を満たすため、ID TokenとUserInfo APIというものを定義している。
- APIプロバイダー側のUser ID取得方法 => ID Token
- APIプロバイダー側のプロフィール情報取得方法 => UserInfo API
ID Tokenは「OpenID Connect Core - Section 2」に定義されているさまざまな情報を含んでいる。例えば、Authorization Server上のエンドユーザーの識別子であるsub
、Tokenの発行先を示すaud
、Tokenの発行者を特定するためのiss
、リプレイ攻撃を防ぐために使われるnonce
、エンドユーザー認証が行われた時刻を表すauth_time
などがある。
ID Token自体は、JSON Web Token(JWT)という方式で署名&エンコードされた、署名付きJSONデータである。JWTのデコードおよび署名検証については、「JWT.io」というサイトに各言語ごとのライブラリがリストアップされているので、それらを使うとよい。
ID Tokenの発行方法はresponse_type
リクエストパラメーターの指定の仕方によって異なるが、Authorization Responseでフラグメント(Fragment)に含めて返されるか、Token Responseに含まれるか、その両方に含まれるかのいずれかになる。これは前回の記事でも紹介したOAuth 2.0 Multiple Response Type Encoding Practicesで定義されているが、簡単に以下に各Response TypeとID Tokenの発行方法(とついでにAccess Tokenの発行方法)についてまとめる。
Response Type値 | ID Tokenの発行方法 | Access Tokenの発行方法 | 備考 |
---|---|---|---|
code |
Token Responseに含まれる | Token Responseに含まれる | Authorization Code(認可コード)フロー |
token |
発行されない | Authorization Responseに含まれる | Implicitフロー OpenID Connectでは利用しない |
id_token |
Authorization Responseに含まれる | 発行されない | Implicitフロー Access TokenはないのでAPIアクセスは不可 |
code token |
Token Responseに含まれる | Authorization ResponseとToken Response両方に含まれる | Hybridフロー |
code id_token |
Authorization ResponseとToken Response両方に含まれる | Token Responseに含まれる | Hybridフロー |
token id_token |
Authorization Responseに含まれる | Authorization Responseに含まれる | Implicitフロー |
code token id_token |
Authorization ResponseとToken Response両方に含まれる | Authorization ResponseとToken Response両方に含まれる | Hybridフロー |
ID Tokenに含まれる属性(Claim)に関して簡単に説明しておくと、もともとの要件であった「User IDが取得できること」という意味ではsub
さえあれば十分なのだが、Token発行先を示すaud
によってToken置換攻撃を防いだり、iss
を含むことでToken Endpointから直接発行された場合以外でも発行者(=署名者)を特定できるようにしたりしている。
またClientが特定の方式に従ったAuthorization Requestを送信した場合には、リプレイ攻撃を防ぐためにnonce
を含めたり、ユーザーが明示的に認証行為(パスワードの入力など)を行った時刻を示すauth_time
を含めたりすることもできる。
よくECサイトでの商品購入確定前にパスワード確認を行うなどといったことがあるが、そういった要件をID連携時にも満たせるように「10秒以内にログインしてなければ明示的にパスワードを聞いてくれ」といった要求を可能にしているのが、このauth_time
(とそれを要求するためにAuthorization Requestに含めるmax_age
リクエストパラメーター)である。
UserInfo APIについては「OpenID Connect Core - Section 5」に定義されている(Section 5.5以降は複雑なユースケースを満たすためのものだが、いったん無視してよい)。
UserInfo APIは、OAuth 2.0で保護されたProfile APIであり、レスポンスフォーマットと各プロフィール項目を要求するためのOAuth Scope値が標準化されたものといえば分かりやすいだろうか。
なお仕様的にはID Tokenにプロフィール情報を含めることも可能となっているが、現実的にはID Tokenにプロフィール情報を入れるケースはまれである。
ID Tokenにnonce
やauth_time
を含めるためのAuthorization Requestに関する仕様は、各フローごとに「OpenID Connect Core - Section 3.1.2.1、3.2.2.1、3.3.2.1」に定義されている。中でもopenid
というScopeは、リクエストがOpenID Connectのリクエストであることを伝えるための、最も重要なパラメーターである。
ID TokenとUserInfo APIという2つの要素を標準化することによって、OpenID ConnectはOAuth 2.0ベースのID連携を「各APIプロバイダー独自のProfile APIへの対応なしに」可能にしており、これが「OAuth 2.0 + Identity Layer = OpenID Connect」といわれるゆえんである。
OpenID Connectユースケース一覧(Clientタイプ別)
さて、ここまでOpenID Connectの概要を見てきたが、ここからは各ユースケースごとのOpenID Connectの利用パターンをまとめていこう。なお「JSアプリ」とは、前回も説明したように「JavaScriptによりブラウザー上で動作するクライアントアプリ」のことで、サーバーサイドで動作する「Webアプリ」とは別タイプのクライアントである。
- Webアプリ
- ネイティブアプリ(アプリ独自のBackend Serverなし)
- ネイティブアプリ(アプリ独自のBackend Serverあり)
- JSアプリ(アプリ独自のBackend Serverなし)
- JSアプリ(アプリ独自のBackend Serverあり)
前回も述べた通り、Backend Serverのないケース2と4では、本質的にユーザー認証のニーズが低く、OpenID Connectを利用することも基本ない。
また純粋にID連携だけに限った場合、一度OAuth Client側でユーザーが認証されてしまえば、それ以降はOAuth Clientが独自にユーザーのセッション管理を行うだけでよいため、OpenID Connectのコンテキストでは「継続的にAPIにアクセスする」といった要件もない。よってケース2と5の差は特にない。
よって本稿ではケース1と3のみを扱う。
Webアプリの場合
このユースケースでは、「OpenID Connect Core - Section 3.1」に述べられているAuthorization Codeフローが用いられる。
このフローをシーケンスにすると図2のようになる。ほぼ図1のFacebook Loginと同じフローとなることが分かるだろう。
Facebook Loginとの相違点は、既存ユーザーがいる場合はUserInfo APIにアクセスしなくてもID Tokenのみで認証が完了できるという点くらいである。既存ユーザーがいる場合でもUserInfo APIにアクセスすることが禁じられるものではないが、特にAPIプロバイダー側でのプロフィール情報更新を同期する必要がなければ、不要なAPIアクセスをスキップできた方がパフォーマンスは良い。
OpenID Connectの仕様は、OAuth 2.0の仕様(RFC 6749)と意図的に重複するように書かれているため長大になっているが、基本的にはこのケースにおいてOAuth 2.0のときと異なる点としては、「OpenID Connect Core - Section 3.1.2.1」にあるopenid
Scopeの利用と、Token ResponseにID Tokenが追加されていること、UserInfo APIのレスポンスが標準化されていることくらいである。
なおOpenID Connectでは、「OpenID Connect Core - Section 5.4」で定義されているprofile
、email
、address
、phone
というプロフィール情報に関連するScopeを定義している。
Authorization Codeフローの場合はToken EndpointからTLSで保護されたチャネル経由で直接ID Tokenが発行されるため、TLSサーバー証明書の検証さえ行っていればID Tokenの改ざんリスクは無視できる。そのためID Token自体の署名検証はOPTIONAL(任意)とされている。また同様にID Tokenがリプレイ(再利用)される可能性も無視できるため、nonce
の利用もOPTIONALとなっている。
よって最も単純なOpenID Connectのユースケースでは、Webアプリの場合はscope=openid
を付けてAuthorization Requestを投げて、返されるID Tokenをデコードして中に含まれるsub
値を基にユーザーを認証するだけである。
ネイティブアプリ(アプリ独自のBackend Serverあり)の場合
ネイティブアプリの場合、OAuth 2.0ではImplicitフロー(response_type=token
)を使うケースとHybridフロー(response_type=code token
)を使う場合があった。
OpenID Connectでもネイティブアプリ側で受け取ったID TokenをBackend Serverに送ることは不可能ではないが、そうするとID Tokenがブラウザーの前のユーザー(正規のユーザーとは限らない)によって改ざんされる恐れがあるため、ID Tokenの署名検証が必須になり、Implicitフロー(response_type=token id_token
)を使うとHybridフロー(response_type=code token
)を利用する場合と比べて複雑になる。
またOpenID Connectのケースでは、response_type=token id_token
をサポートしながらresponse_type=code token
をサポートしていない実装はまずないため、必ずと言ってよいほどHybridフローが利用できる。
よってOpenID ConnectではHybridフローのみを利用すればよい。
OpenID ConnectにおけるHybridフローは、前掲の表2にあるように3つのパターンが存在し、その全てが「OpenID Connect Core Section 3.3」に同時に書かれているため、仕様は多少読みづらいかもしれない。
response_type=code token
の場合のフローをシーケンスにすると図3のようになる。
このケースでもOAuth 2.0と異なるのは、openid
Scopeの利用と、Token ResponseにID Tokenが追加されていること、UserInfo APIのレスポンスが標準化されていることくらいである。
ここでもID Tokenは、Token EndpointからTLSで保護されたチャネル経由で直接ID Tokenが発行されるため、署名検証やnonce
の利用はOPTIONALである。
その他のResponse Typeの使い道(少し発展編)
以上のように、基本的にはresponse_type=code
とresponse_type=code token
が利用できれば、主要なユースケースは満たすことができる。ではそれ以外のResponse Typeにはどのような使い道があるのだろうか?
response_type=token
はOpenID Connectでは利用しないため無視すると、それ以外は全てID TokenをAuthorization Responseに含めるタイプのResponse Typeである。JSアプリにしろ、ネイティブアプリにしろ、クライアントサイドでユーザー認証が必要になるケースはほぼ存在しないため、クライアントサイドでID Tokenを使うモチベーションもユーザー認証というコンテキストではほぼ存在しない。
一方で、ID Tokenは以下のような属性を含むことができ、部分的なレスポンスパラメーターの置換・改ざんを検知するために利用できる。
- 同時に発行されたAccess Tokenのハッシュ値である
at_hash
- レスポンスパラメーター中のAccess Tokenが置換・改ざんされているとハッシュ値がズレる
- 同時に発行されたAuthorization Codeのハッシュ値である
c_hash
- レスポンスパラメーター中のAuthorization Codeが置換・改ざんされているとハッシュ値がズレる
- 特定のAuthorization Requestと特定のID Tokenをひも付ける
nonce
- 別のAuthorization Requestにひも付いたID Tokenに置換されていると
nonce
値がズレる
- 別のAuthorization Requestにひも付いたID Tokenに置換されていると
個別ケースの詳細なリスク分析は割愛するが、例えば図3のフローでAuthorization Response中のAccess Tokenだけが別ユーザーのものに置換されていたとすると、ネイティブアプリ(OAuth Client)が持っているAccess TokenとBackend Server(OAuth Client Backend)が持っているAccess Tokenが、Authorization Server上では別のユーザーにひも付いた状態になる。
そういった状態が発生すれば、当然ネイティブアプリ側からAPIアクセスした場合とBackend ServerからAPIアクセスした場合で、矛盾した結果が引き起こされることになる。
例えば金融系のAPIであれば、ネイティブアプリから振込APIを叩いた場合とBackend Serverから振込APIを叩いた場合で、異なる口座から振込処理が行われ得る。
そういったリスクを避けたい場合は、ID Tokenを認証目的以外で利用するといったケースもあり得るのである。
興味がある方は、OpenID Foundation Financial API(FAPI)WGが策定を進めているOAuth Security Profile(Read OnlyとRead & Writeの2種類あり)などを読んで見るとよいだろう(2017年7月段階では未翻訳)。
OpenID Connectユースケース一覧(認証パターン別)
ID連携の場合には、Clientのタイプ以外にも、いくつかの代表的なユースケースが存在する。ここではそのうち以下の2つのユースケースについて、OpenID Connectの利用方法をまとめる。
- Single Sign On(SSO)
- 再認証・強制認証
ユーザーインタラクションなしで認証できる場合は即座にログイン済にさせたい ― Single Sign On(SSO)
いわゆる「Single Sign On(SSO)」と呼ばれるものの一種では、以下の2つの要件を同時に満たしたいというケースがある。
- ユーザーがAuthorization Serverにログイン済みであれば、ユーザーがOAuth Clientにアクセスした瞬間、ユーザーをログイン済みにさせたい
- ユーザーがAuthorization Serverに未ログインであれば、ユーザーがOAuth Clientにアクセスしたときには、ログインボタンを表示したい
OpenID Connectでは、原理的にユーザーをAuthorization ServerにリダイレクトさせずにOAuth ClientがAuthorization Server側のセッション状態を知ることはできない。一方でAuthorization ServerにリダイレクトしてしまうとAuthorization Server側のログイン画面が表示されてしまい、OAuth Client側でログインボタンを表示することはできない。
この問題を解決するため、OpenID Connectはprompt
リクエストパラメーターを定義し、その値としてnoneを指定することで、ユーザーがAuthorization Server側でログイン済みかつ同意済みであれば成功レスポンスを、そうでなければエラーレスポンスを返すという機能を定義している(「OpenID Connect Core Section - 3.1.2.1」参照)。
前回のOAuth 2.0の記事で、JSアプリ向けにhidden iframeを使うテクニックを紹介したが、あれがまさにこのprompt=none
リクエストパラメーターを利用するケースである。
prompt
はOpenID Connectによって定義されたパラメーターであるが、OpenID Connectのコンテキスト外で通常のOAuth 2.0の場合にも利用できることが多い。
なお、prompt=none
を指定してerror=login_required
もしくはerror=consent_required
というエラーが返された場合は、ユーザーインタラクションが必要という合図であり、その場合OAuth Clientはログインボタンを表示することになる。
再認証・強制認証
前節のユースケースとは逆に、ユーザーがAuthorization Server上で認証済みかつ同意済みであっても、強制的に認証をさせたいという場合もある。
これは共有PCなどでログアウトし忘れたまま離席した際に、当該マシンを第三者に悪用されるといったリスクに対する対抗策として利用されることが多い。
このケースでは、prompt
パラメーターにloginやconsentといった値を指定することで、Authorization Serverに簡易的に認証/同意画面を強制表示させることができる。
しかしながら厳密にはOAuth 2.0やOpenID Connectのリクエストパラメーターはブラウザー操作者によって改ざん・追加・削除が可能なので、prompt=login
というリクエストパラメーターを削除されてしまっては意味がない。
よって、そのようなときにはmax_age
というリクエストパラメーターを利用する(「OpenID Connect Core Section - 3.1.2.1」参照)。
max_age
を利用すると、最終的にID Tokenにauth_time
という属性が含まれて返されるため、この属性の有無およびその時間をチェックすることで、確実に再認証が行われたことが確認できる。
なおその際はAuthorization Server上で認証が終わってからOAuth Clientがその結果を受け取るまでの若干のタイムラグを許容するよう実装すること。
最後に
ここまで2回にわたってOAuth 2.0とOpenID Connectの概要やユースケース毎の使い方について見てきた。
OAuth Client側の実装者にとっては、この2回の記事でほとんどのユースケースは満たされるものと期待している。
一方で、Authorization Server側を実装する必要がある事業者など、より深い理解を必要とする場合があれば、ぜひOpenID Foundation Japanに声をかけてほしい。また英語に不自由しない場合は、直接OpenID Foundation(Global)にコンタクトしてもよいだろう。
OpenID Foundation(Global)では、OpenID Certificationプログラムの一貫としてOpenID Connect IdP/RP Conformance Test(適合性試験)ツールや主要言語ごとの適合度試験合格済みOpenID Connectライブラリの公開を行っているので、それらのツールもぜひ活用してほしい。
1. OAuth 2.0の代表的な利用パターンを仕様から理解しよう
仕様策定から5年がたったOAuth 2.0の現状と概要を紹介。「Webアプリ」「ネイティブアプリ」「JavaScriptアプリ」といったOAuth 2.0の各種ユースケースについて、仕様を読み解きながら説明する。