はじめに
OAuth2.0とOpenIDConnectについて勘違いを起こしやすいところについてまとめました。
OAuth2.0とOpenIDConnectの関係性
よく OAuth/OIDCと書かれたドキュメントを見ますが、この二つは明確に異なる役割を持つプロトコルになります。
この二つの関係性を表す文がOpenIDConnectの仕様に書かれています。
OpenID Connect 1.0 は, OAuth 2.0 プロトコルの上にシンプルなアイデンティティレイヤーを付与したものである. このプロトコルは Client が Authorization Server の認証結果に基づいて End-User のアイデンティティを検証可能にする. また同時に End-User の必要最低限のプロフィール情報を, 相互運用可能かつ RESTful な形で取得することも可能にする.
OIDCはエンドユーザのアイデンティティをID Provider(idp)が検証し、その結果をtokenの形で発行し、伝達することができるプロトコルです。なぜOAuthと並列に語られるかというと、そのプロトコルのほとんどがOAuthと近しい構成をとっているためです。つまりOAuthと同じ仕組みを用いてエンドユーザの証明(つまり認証)をする仕組みを作ったというのがOpenIDConnectになります。このエンドユーザの証明手形のことを ID Token と呼びます
これに対して、OAuthは権限の移譲を行うプロトコルです。ID Providerは特定のリソースに対して、そのオーナーの許諾を元に、アクセス権限を別のシステムに発行します。この時のアクセス権限手形のことを Access Token と呼びます。OAuthはその基本仕様をRFCで規定されています。
OIDCとOAuthはこのように、全く目的の異なるものであるということを理解いただければと思います。
使い方編
OAuthの目的
前述の通り、OAuthは権限の移譲を行うプロトコルです。移譲と言われてもわかりづらいと思うので、以下のシチュエーションを考えます。
ストレージサービスA(サービス事業者A)を利用し、ファイルをたくさん登録している X さん。このXさんはサービスA上のファイルリソースのオーナーです。このファイルストレージの情報を使い、別のサービス事業者BがサービスBを提供したいと思いました。
この場合にサービスBを実現するための方法は、サービスBがストレージサービスAを利用するためのクレデンシャルを取得することです。
もし、XさんがID,パスワードをサービスBに教えてしまうと、サービスBはAさんのストレージサービスを自由に利用できてしまうことになります。これでは権限が大きすぎます。
求めているのは「サービスBに必要な分だけ、サービスAへのアクセス権限をリソースオーナーから移譲して欲しい」ということです。この時にまさに利用できる選択肢としてOAuthが登場します。OAuthが発行するAccess Tokenは合鍵の機能を果たします。リソースオーナーが許可したサービスAの利用範囲(スコープ)に限り、Access Token(合鍵)が作成され、作成された合鍵がサービスBに提供されるという処理がなされます。
間違えやすいのですが、この場合 OAuth機能を提供するID Providerを公開するのはサービス事業者A となります。
「ストレージサービスAの機能を他アプリから利用したい・させたい」 という要件があるから 事業者AはOAuthを採用し、ID Provider機能を提供するということになります。逆にいうと、この要件がない限り、OAuth を構築する必要はありません。
OpenID Connectの目的
OpenIDConnectではエンドユーザの証明を行い、証明手形を発行するためのプロトコルです。今回はOAuthよりシンプルで、その目的はエンドユーザの認証処理部分をサービス事業者の提供サービスから外部に分離するということです。
あるサービス事業者Cがいたとします。このサービス事業者Cは利用者を特定するため、ユーザ登録とログインを前提としたサービスを提供したいとします。この時一番簡単な方法は、サービス事業者Cがサービスに認証機能を持たせることです。
ただ、認証機能はユーザ情報やパスワードを扱うため、高い技術を要求される上にさまざまなサービスを抱える事業者がそれぞれのサービスで認証機能を運用するのはコストが高いです。そこで、サービス事業者Cはログイン機能をID Providerとして分離し、分離先のID Providerから発行された証明手形となるID Tokenを発行してもらって、その正当性を検証して問題なければユーザがログイン状態になるように設計しました。サービス事業者Cは既存の抱えていた複数のサービスからもログイン機能をこのIDPに集約し、開発リソースの集約やセキュリティの向上、Single Sign Onなどの機能提供など集約化のメリットを享受しました。
この分離後の状態で、サービス事業者Cが提供する認証機能を抜き取られたサービスのことを Relying Partyと呼びます。外部ID ProviderにIDを提供する機能を頼る状態となったため、こう呼びます。
このOpenIDConnectはオープンな規格なので、GoogleやFacebookなど巨大サービスに存在する自信のアカウントの証明手形を元にRelying Partyを実装し、ログインを認めるということも可能です。その場合、自社でのID Providerの運用を行う必要すらなくなります。
これがOpenIDConnectのユースケースになります。
Token編
id tokenとaccess tokenの違い
OpenIDConnectが発行するID Token と OAuthの発行する Access Token の一番の違いは「トークンを検証する人」が異なることです。
Access Tokenの場合、検証する人はリソースサーバになります。先ほどのストレージサービスAの例でいうと、ストレージサービスAの管理するファイルリソースにアクセスするためのAPIを指してリソースサーバと呼びます。
これはよく考えると当たり前ですが、機能を提供するのはリソースサーバなので、この権限移譲のための合鍵となるAccess Tokenを検証するのはリソースサーバになります。
一方で、ID Tokenの場合、検証する人はRelying Partyです。Relying Partyは自分ではユーザを検証することができませんが、代わりにIDProviderからユーザの証明手形となるID Tokenを発行してもらい、その検証を行なったのち該当ユーザをログイン状態に変更する処理を行うのです。そのためID Tokenの検証を行うのはRelying Partyです。
検証とは何か
検証といきなり書きましたが、トークンの検証作業とは具体的になんなのでしょうか?
実は Open ID Connect の提供するID Token と OAuth の提供するAccess Tokenのそれぞれのトークン検証作業は異なります。
まずAccess Tokenですが、Access Token中にはScope(スコープ)という権限移譲を許された範囲を示す値が存在します。Access Tokenを持つクライアントが行っていい範囲が記されており、それ以上のことを行わないようにリソースサーバはこの値を超える動作をクライアントアプリが要求してきていないかをチェックします。この動作により権限移譲が成立します。
ここで問題が発生します。クライアントアプリケーション上でAccess Tokenが改竄された場合にはどうでしょうか?改竄防止に利用する標準技術であるデジタル著名の技術がこのAccess Tokenにも施されており、改竄が検出できるようになっています。
デジタル著名の技術ではAccess Token発行者との非対称鍵交換の必要性があります。通常、OAuthを提供するID Providerはこの非対称鍵のうち公開鍵の情報を提供する機能を有します。
このようにID Providerは外部に対して提供しているさまざまな情報(エンドポイントの情報含む)を提供する必要があるため、これらの情報をまとめて取得できるOpenID Configurationエンドポイントを有しています。
Yahooの例では以下となります。(JWKsと書かれている部分が上記非対称鍵の取得のためのエンドポイント)
次はID Tokenです。デジタル著名など基本的な部分は一緒ですが、大きく異なるのはScopeです。
id tokenは証明手形であり認証機能の実装に使います。リソースサーバへのアクセスを検証するわけではないため、検証内容にリソースへのアクセスの範囲などは入りません。ID Providerを提供しているYahoo社がID Tokenの検証方法の例を載せてくれていましたので、こちらを参照いただくのが早いかと思います。
OpenID Connectで発行されるAccess Tokenについて
これは、一番混乱するところと思います。
OpenID Connectのフローは証明手形ですが同時にAccess tokenも発行されるケースがあります。この文脈で語られるAccess Tokenには2種類あります。
UserInfoエンドポイント用のAccess Token
前提としてID Tokenを発行しているので認証用のID Providerです。この認証用IDPは通常、認証手形を発行したエンドユーザの情報を提供するエンドポイントを備えています。これは UserInfoエンドポイントと言いますがこのエンドポイントはリソースサーバと見ることができ、Relying PartyからOIDCの際にID Tokenと一緒に発行したアクセストークンを用いてアクセスを行うことが可能です。トークンリクエスト時に
scope=openid profile email
などのリクエストを行うことで可能です。
Relying Partyが外部リソースサーバにアクセスするためのAccess Token
外部API等のリソースサーバに対するAccess Tokenを本当に発行するパターンです。Relying Partyが外部リソースサーバにアクセスを求められる場合です。以下のサイトなどにコメントがありました。
アーキテクチャ編
OAuthクライアントタイプについて
OAuthのクライアントタイプについての議論については多数の記事がネット上に溢れています。RFCでは以下のセクションで語られています。
本当に簡単な整理としては、以下です。
Client Type | 説明 | 例 |
---|---|---|
Confidential | 鍵の機密性を維持できるクライアント | Backendを持つWebアプリケーション |
Public | 鍵の機密性を維持できないクライアント | SPA構成のWEB、ネイティブアプリ |
ここで前提として、通信の機密性やリバースエンジニアリングなども含めた機密性の維持ということです。鍵をユーザ側に保存する前提の構成はPublicにしかなり得ないということですね。
以下の記事もわかりやすいです。
OAuthのクライアントタイプが何に聞いてくるかというと、以下の2点です。
クライアント認証
クライアント認証とは、クライアントアプリケーションを識別するための方法です。
一番基本的な方法としてクライアントシークレットというID Providerとクライアントアプリケーションの間で合言葉を決めておくという実装方法があります。(合言葉をClient Secretと呼びます)
また他の方法もいくつか存在します。例えばJWT形式のアサーションという方法がRFCで規定があります。
このクライアント認証ですが、前述のように鍵の機密性が維持できないクライアントについては”合言葉”の機密性が問われてしまうため、基本的にConfidential Client 用の機能と言えます。
スコープ
Confidentialなクライアントに対してPublicなクライアントは前述のクライアント認証を厳密に行うことができません。そのため、Publicなクライアントにシステム上クリティカルな権限を発行することは危険という判断でAccess Tokenにより付与するスコープを分けることが考えられます。
例えば金銭のやり取りなどが発生するような例です。
トークンをどこに保存するか
この議論もよく起きます。
この議論に至る前提として、これまで掲載したAccess Tokenおよび ID Tokenは持っている限り有効になるものです。Access Tokenの検証はリソースサーバで行うが、保持しておかないとそもそもリクエストにトークンを添付できないためです。そのため期限の管理など含め、クライアントアプリケーション/Relying Partyの手の届く領域に置いておくことが求められます。
ここで、考えをPublicなクライアントとConfidentialなクライアントで分ける必要があります。
Publicなクライアントについては、鍵を安全に保管することができないことからもわかるようにトークンも安全に保管することができません。そのため、保存場所ごとにリスクを分析し、保存場所を決定する必要があります。
SPA構成のWebを想定してみると、使えるのはブラウザ側の機能もしくはSPAのコードとなりますので、保存できる領域はCookie、Session Storage、LocalStorage、オンメモリなどの選択肢となります。一番堅牢な保存先(かつ色々なサイトで推奨されている保存先)はコード中でオンメモリに保存することですが、ブラウザのリロードによりトークンが消えてしまうデメリットがあります。
ではリロードで消える保存先を選んでいいのか?という話になりますが、 OAuthやOpenID Connectには再度ID Providerに問い合わせることなくトークンを更新する仕組み があります。これがリフレッシュトークンです。RFCでも規定があります。
このRefresh Tokenさえ持っていればリロードによりAccess Tokenが消えても簡単に再発行してもらえるため、権限の移譲を継続してもらえるということですね。
このリフレッシュトークン自体も安全な領域に保存しておく必要はありますが、SPAの場合には選択肢は変わらずCookie、Session Storage、LocalStorage、オンメモリとなりますので、ある程度長めに保存できるLocal Storageなどが選ばれる傾向にあるのかなと思います。
トークン流出について
各々のトークンの使い方を見ていくと
- idtoken: Relying Partyで保持して利用する
- access token: リソースサーバに投げて利用する
- refresh token: ID Providerに投げて利用する
という使い方をします。このうち、一番流出する恐れがあり危険なのはaccess tokenです。理由としてはリソースサーバは複数存在し、そのどれかがセキュリティの侵害を受けた場合でもこのaccess tokenは流出し、第三者に利用される恐れがあるからです。
このため、トークン発行要求クライアントとトークンを利用しているクライアントが同一かを判断するための仕組みが用意されています。
この仕組みを Proof of Possession と呼びます。RFCでもコメントがあります。
Backend for Frontend構成について
クライアントタイプの整理で見たように Public クライアントの問題点は機密情報となる鍵を安全に保持できないことでした。そのため、機密情報となる鍵をバックエンドに保持し、バックエンドをOAuthのClient/OpenIDConnectのRelying Partyとすることで、ID Providerからみてバックエンドが代わりにConfidential Clientとなるという方法があります。
この方式ではバックエンドがパブリッククライアントアプリケーションの代わりにID Providerと通信を行いますので、当然ながらその実装は必要です。また、従来想定していたSPAなどのパブリッククライアントとバックエンドの間の通信は独自に規定する必要があります。
このアプリケーション構築のパターンについては、RFCのドラフトがあります。
おわりに
今回は文字と参考ドキュメントを中心に OAuth/OpenID Connectで議論されていることを体系的に整理したつもりです。いろいろなサイトで答えが掻き切られていなかったり誤った認識が書かれていることもあるので、一度まとめてみました。
実は本記事では
- OAuthやOpenID Connectが実際にどのような通信を行うのか(フロー)
- トークンの詳細
については触れていません。これらは具体的な内容になるので、また別の機会にまとめられればと思います。
もしくはフローについてはAuthleteさんの記事がわかりやすいかなとは思っております。本記事を読んだ後に目を通してもらうと理解がしやすいのかなと思っています。
参考資料
OpenIDConnectのドキュメント
OAuth2.0 の Internet Engineering Task Force (IETF) のRFCドキュメント
Bearer Token のRFCドキュメント