はじめに
OAuth2.0とOpenIDConnectを混同されて語られる文脈が多いですが、この両方の実装を経験した身としてはやっていることは実装レベルでは全く異なります。そのことについてまとめてみたいと思います。
この記事でわかること
- 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はエンドユーザがエンドユーザであることを証明するためのプロトコルです。
これに対し、OAuthは権限の移譲を行うプロトコル です。OAuthはその基本仕様をRFCで規定されています。
なぜOpenIDConnectがOAuthと並列に語られるかというと、ドキュメントにも書かれている通りOAuthの仕組みの上にエンドユーザの証明(つまり認証)をする仕組みを上乗せして作ったというのがOpenIDConnectになるからです。
使い方編
この章の概要
- OAuthは権限をトークンとして与えるプロトコル
- OpenIDConnectはユーザの証明書をトークンとして与えるプロトコル
- OAuthで発行されるトークンをAccessToken, OpenIDConnectで発行されるトークンをIDTokenと呼ぶ
OAuthの目的
OAuthは権限の移譲を行うプロトコルです。移譲と言われてもわかりづらいと思うので、以下のシチュエーションを考えます。
カレンダーサービスA(A社)を利用し、予定をたくさん登録している X さん。XさんはサービスAで自分の予定を管理しているオーナーです。ある日、このカレンダー情報を使って別のB社がサービスBを提供したいと思いました。
この場合にサービスBを実現するための方法は以下のパターンがあります
- サービスBにカレンダーサービスAのXさんのログイン権限を与え, サービスAを使えるようにする
- サービスBからサービスAへXさんのカレンダーの特定の操作権限を移譲する
前者でサービスBはXさんのアカウントにログインしてサービスを使っているわけですので、サービスA上でできる全てのことが行えてしまうのです. 例えばXさんのカレンダーの予定を書き換えることもできてしまうわけです. これは最小権限の法則に明らかに反しています.
では後者の方が望ましいとして, まさにこのケースを実現するための選択肢としてOAuthが存在するわけです.OAuthが発行するAccessTokenは合鍵の機能を果たします。リソースオーナーXさんが許可したサービスAの利用範囲(scope)に限りAccessTokenが作成され、作成された合鍵がサービスBに期限付きで提供されるのです。
注意点として、この場合 OAuth機能を提供するID Providerを公開するのはサービスA となります。
「サービスAの機能を他アプリから利用したい・させたい」 という要件があるから OAuthを採用しID Provider機能を提供するのです。逆にいうとこの要件がない限り必ずしも OAuth の仕組みを外部に提供する必要はありません。
OpenID Connectの目的
OpenIDConnectではエンドユーザの証明を行い、証明手形を発行するためのプロトコルです。OAuthより目的はシンプルで、エンドユーザの認証処理部分をサービス事業者のサービスから外部に分離することです。
あるサービス事業者Cがいたとします。このサービス事業者Cは利用者を特定するため、ユーザ登録とログインを前提としたサービスを提供したいとします。この時一番簡単な方法は、サービス事業者Cがサービスに認証機能を持たせることです。ただ、認証機能はユーザ情報やパスワードを扱うため、高い技術を要求される上にさまざまなサービスを抱える事業者がそれぞれのサービスで認証機能を運用するのは高コストです。
サービス事業者Cはログイン機能をID Providerとして分離し、ID Providerからエンドユーザの証明手形としてIDTokenを発行してもらいその正当性を検証してユーザの手元のサービスがログイン状態になるように設計したいとします。
このID ProviderからIDTokenを発行してもらうために利用するプロトコルが OpenIDConnectとなります.
サービス事業者Cは既存の抱えていた複数のサービスからもログイン機能をこのIDPに集約し、開発リソースの集約やセキュリティの向上、Single Sign Onなどの機能提供など集約化のメリットを享受することができます。
このとき、IP Providerと通信するクライアントのことを Relying Party と呼びます。外部ID ProviderにIDを提供する機能を頼る状態となったためこう呼びます。
このOpenIDConnectはオープンな規格のため、GoogleやFacebookなど巨大サービスに存在する自身のアカウント証明手形を元にRelying Partyを実装し、ログインサービスを実装することも可能です。その場合、自社でID Providerの運用を行う必要がなくなります。
フロー図の比較
OAuth2.0の認可コードフロー
OpenIDConnect
似たフローとなっていることがわかります。
トークン編
id tokenとaccess tokenの違い
両方のフローの「2. 認可コードを使ってトークン要求」処理のID Provierからのレスポンスとして取得するデータがトークンとなります。ここで複数の種類のトークンがあるため、違いを確認していきます.
- OAuthの発行するのはAccessToken
- OpenIDConnectが発行するのはIDToken
一番の違いは「トークンを検証する人」が異なることです。この検証する対象のことを Audience と呼びます.
さきほどのフロー図でいうとAccessTokenの場合、検証は「3.のAPI呼び出し」でサーバ側が行います. この呼び出されるAPIのことをリソースサーバと呼びます.このリソースサーバがAudienceとなります。
一方、IDTokenの場合にはログインかどうかを判定して続けた処理や画面を変えないといけないのはトークンを受け取ったクライアント(Relying Party)のため「3. id_tokenの署名検証 & nonce照合」で検証をします. そのためAudienceはRelying Partyとなります。
トークン検証とは何か
検証といきなり書きましたが、トークンの検証作業とは具体的にどういった作業でしょうか?
実はそれぞれトークン検証作業は異なります。
まずAccessTokenですが、AccessToken中にはScope(スコープ)という権限移譲を許された範囲を示す値が存在します。AccessTokenを持つクライアントが行っていい範囲が記されており、それ以上のことを行わせないようにリソースサーバはこの値を超える動作をクライアントアプリが要求してきていないかをチェックします。
クライアントアプリケーション上でAccess Tokenが改竄された場合にリソースが自由に奪われてしまうと問題ではないでしょうか?その対策として、改竄防止に利用する標準技術であるデジタル署名がこのAccess Tokenにも施されており、改竄が検出できるようになっています。
デジタル署名では署名を行うID ProviderとAudienceでAccess Token発行者との非対称鍵交換の必要性があります。通常、OAuthを提供するID Providerはこの非対称鍵のうち公開鍵の情報を提供する機能を有します。
このようにID Providerは外部に対して提供するさまざまな情報(エンドポイントの情報含む)を公開する必要があるため、これらの情報をまとめて取得できるOpenID Configurationエンドポイントを有しています。
Yahooの例では以下となります。(JWKsと書かれている部分が上記非対称鍵の取得のためのエンドポイント)
次にID Tokenです。デジタル著名など基本的な部分は一緒ですが、大きく異なるのはScopeです。
id tokenは証明手形であり認証機能の実装に使います。リソースサーバへのアクセスを検証するわけではないため、検証内容にリソースへのアクセスの範囲などは入りません。ID Providerを提供しているYahoo社がID Tokenの検証方法の例を載せてくれていました
OpenIDConnectで発行されるAccess Tokenについて
「あれ、OpenIDConnectってIDTokenじゃないの?」と思われるかもしれませんがAccess Tokenも含まれる場合があります。
OpenID Connectのフローは証明手形ですが同時にAccess tokenも発行されるケースがあります。この文脈で語られるAccess Tokenには2種類あります。
UserInfoエンドポイント用のAccess Token
認証用ID Providerは通常、認証手形を発行したエンドユーザの情報を提供するUserInfoエンドポイントと呼ばれるAPIを備えています。このエンドポイントへのリクエスト時には検証が必要です。
Relying PartyからこのUserInfoエンドポイントへのアクセス時に利用するためのトークンとしてAccess TokenをOpen ID Connectのレスポンスとして返します。
フロー図の手順でいうと「4. ユーザープロファイル取得」がこの処理にあたります. この処理はマストではないですが、ID Providerが管理するユーザ情報をRelying Partyが使用したい場合に利用ができます.
scope=openid profile email
などのリクエストを行うことで可能です。
Relying Partyが外部リソースサーバにアクセスするためのAccess Token
Access Tokenを本当に発行するパターンです。Relying Partyがログインの処理に追加して外部リソースサーバにもアクセスを求められる場合です。以下のサイトなどにコメントがありました。
アーキテクチャ編
本章はOAuthとOpenIDConnectで共通となるものが多い.
クライアントと認可サーバ間での通信およびそのレスポンスに対してのクライアントの挙動などがアプリ構築環境などにより複数考えられるため, それらを整理する
OAuthクライアントタイプについて
OAuthのクライアントタイプについての議論については多数の記事がネット上に溢れています。RFCでは以下のセクションで語られています。
簡単な整理としては以下
Client Type | 説明 | 例 |
---|---|---|
Confidential | 鍵の機密性を維持できるクライアント | Backendを持つWebアプリケーション |
Public | 鍵の機密性を維持できないクライアント | SPA構成のWEB、ネイティブアプリ |
通信の機密性やリバースエンジニアリングなども含めた機密性の維持ということです。鍵をユーザの手元に保存する構成はPublicにしかなり得ないということですね。
以下の記事もわかりやすいです。
クライアント認証
ユーザ認証ではないので注意
クライアント認証とはIDProviderがクライアントアプリケーションを識別するための方法です. 不正なアプリからIDProviderがリクエストを受け付けないために実施する判定だと思っていただければと思います.
- クライアントシークレットというIDProviderとクライアントアプリケーションの間で合言葉を決めておくという実装(合言葉をClient Secretと呼びます)
- JWT形式のアサーションという実装 (RFCで規定がある)
このクライアント認証は鍵の交換が必須となります. そのため鍵の機密性が維持できないクライアントについては意味がなく基本的にConfidential Clientについての機能と言えます。
認可リクエスト自体の改ざん
クライアントからリクエストを送る際に、そもそもブラウザなどで認可リクエスト自体の改ざんが行われるとセキュリティのリスクとなりえます. そのため認可リクエスト自体の改ざんや漏洩を防ぐためにクライアントから認可リクエストの内容を先に送信することを可能にした仕様をPAR(Pushed Authorization Requests)と呼びます.
以下が日本語で解説してくれているページとしてわかりやすかったページです.PARの仕様では認可リクエストの前にパラメータを直接送信し, そのレスポンスを用いてその後の認可リクエストを送信することでパラメータとの紐付けを行う.
スコープ管理
Publicなクライアントは前述のクライアント認証を厳密に行うことができません. Publicなクライアントにシステム上クリティカルな権限を発行することは危険となります. 対策としてConfidentialなクライアントとPublicなクライアントでAccess Tokenに付与するスコープを分けることが考えられます。
例えば
- 金融システムの管理画面はConfidential Clientで権限を豊富に
- 貯金管理システムはPublic Clientで読み取り権限のみ
など
トークンをどこに保存するか
前提として、これまで掲載した内包型AccessTokenおよび IDTokenは期限が来ない限り持っていれば有効になるものです。つまりクライアントはこれらのトークンについて漏洩を避け安全に管理する必要があります.
- Publicなクライアントについては安全に保管することができません
- Confidentialなクライアントについては鍵同様の安全な管理先でトークンを管理してください
前者PublicなクライアントについてSPA構成のWebを想定してみると、使えるのはブラウザ側の機能もしくはSPAのコードとなります. 保存できる領域はCookie, Session Storage, LocalStorage, オンメモリの選択肢となります。
OAuthやOpenID Connectには再度ID Providerに問い合わせることなくトークンを更新する仕組みがあり、このために使う特殊なトークンをRefresh Token(リフレッシュトークン)と呼びます。RFCでも規定があります。
このリフレッシュトークン自体も安全な領域に保存しておく必要はあります
保存先についてはセキュリティやサービス特性を考慮して決めるしかありませんが、一番堅牢な保存先(かつ色々なサイトで推奨されている保存先)はコード中でオンメモリに保存することが提案されているケースが多いかと思います。ただしこのケースではブラウザリロードによりトークンが消えてしまうデメリットがあります。またRefresh TokenはAccess Tokenよりも永続化できるストレージに保存しておく必要がありますので、現実的にはLocalStorageなどの長期的に保存されている実装が多いのかなと思っております。
なおAccess TokenはBearerトークン(トークンを持っている人なら誰でも利用できるトークン)と呼ばれ基本的に通信時のAuthorization Headerに付与して利用するため、Cookieに保存して通信のたびに送信する仕組みは取らないことに注意です。
Sender-Constrained Token
各々のトークンの使い方を見ていくと
- idtoken: Relying Partyで保持して利用する
- access token: リソースサーバに投げて利用する
- refresh token: ID Providerに投げて利用する
という使い方をします。このうち、一番漏洩する恐れがあるのはaccess tokenです。理由としてはAccess Tokenが不特定多数のAPIに投げて使うからです。APIなどのリソースサーバは複数存在し、そのどれかがセキュリティの侵害を受けた場合でもこのaccess tokenは漏洩し、第三者に利用される恐れがあります。
この改善のためトークン発行要求クライアントとトークンを利用しているクライアントが同一かを判断するための仕組みが用意されています。
この仕組みを Proof of Possession(POP) と呼びます。送信者が限定されたトークンをsender-constrained token, つまり盗まれても使えないトークンと呼び以下のRFC9449で規定されています.
Backend for Frontend構成について
Public クライアントの問題点は機密情報となる鍵を安全に保持できないことでした。
そのため、ID Providerからみて通信相手をバックエンドサーバとし、Confidential Clientとするという方法があります。この場合にはID Providerとの通信に必要な鍵などの情報はバックエンドに保存し、バックエンドとID Providerで通信を行い、クライアントサービスはバックエンドとのみ通信するということになります。このバックエンドのことをBackend for Front Endと呼びます。この方式の際には、SPAなどのPublic Clientとバックエンドの間の通信は独自に規定する必要があります。
このアプリケーション構築のパターンについては、RFCのドラフトがあります。
FAPI について
アーキテクチャ部分であげた内容に通じる仕様としてFAPIというドキュメントが存在します. これはFinancial業界のようにセキュリティリスクに対して高度な対策が求められる業界に対しての満たすべき機能を定義したものです. 今回掲載したようなRFCのうちどれを満たすべきかが掲載されています.
FAPIは現在時点でFAPI2.0が公開されています.
おわりに
今回は文字と参考ドキュメントを中心に OAuth/OpenID Connectで議論されていることを体系的に整理したつもりです。いろいろなサイトで答えが掻き切られていなかったり誤った認識が書かれていることもあるので、一度まとめてみました。