認証・認可の仕組みである OAuth や OIDC (OpenID Connect) まわりは何かと説明する機会が多いので、ポイントを絞ってまとめておきます。 これを理解すればわかった気になれるという説明です。 サービスの全体構成を考えるときなどに使える一枚絵ができるといいなぁ。
認証と認可
基本をおさらい。
- 認証 (Authentication)
- ユーザーが誰であるかを確認するプロセスです。 例えば、ユーザー名とパスワードを使ってログインすることが認証の一例です。
- 認可 (Authorization)
- 認証されたユーザーが、クライアント(アプリケーション)に対して、どのユーザーリソースにアクセスできるか、どの操作を実行できるかを決定するプロセスです。 勘違いされがちですが、認可の対象はあくまでクライアント(アプリケーション)であり、ユーザーではありません。 ユーザーが、第三者のアプリに対して自分のデータにアクセスしてもいいよ、と許可を与えることです。
OAuth と OIDC
OAuth
2012 年に RFC 6749 として OAuth 2.0 が公開されました。 「RFC むなしく」と覚えます(覚えなくてもよいけど)。
OAuth は認可のための HTTP ベースのフレームワーク で、ユーザーのリソース(データ)へのアクセス権を、特定のアプリケーションに付与するための仕組みを提供します。
例えば、Google カレンダーの情報を扱うアプリを作ったとすると、ユーザーは Google の認可画面で「このアプリに自分のカレンダーデータへのアクセスを許可する」ことを同意します。 構図としては、ユーザー自体にアクセス権限を与えているわけではなく、アプリ(OAuth 用語ではクライアント)にリソースへのアクセス権を委譲している という点を理解してください(混同しがちなところ)。 結果として、そのアプリはユーザーの代わりに Google Calendar API などを呼び出すためのアクセストークンを取得し、許可された範囲(スコープ)内でデータにアクセスできるようになります。
OIDC (OpenID Connect)
2014 年に OpenID Foundation が OpenID Connect Core 1.0 を公開しました。
OAuth 2.0 は本来「認可(authorization)」のためのフレームワークであり、ユーザー認証(authentication)を直接目的とした仕様ではありません。 しかし実際には、アクセストークンやスコープを流用して「ログイン」のような用途で使われることが多くありました。
OpenID Connect(OIDC)は、この「OAuth 2.0 の上で動作する認証レイヤー」 として定義された仕様です。
具体的には、ID トークンという JWT 形式のトークンや、それを発行・取得するためのエンドポイント(/authorize、/token、/userinfo など)、およびクレーム(ユーザー情報)の標準的な定義を追加しています。
OpenID Connect (OIDC) は OAuth 2.0 を拡張する形で設計されているため、OIDC を利用するということは、基盤として OAuth 2.0 も同時に利用していることになります。
登場人物と用語
下記は OAuth 2.0 の文脈で出てくる 4 つの構成要素です。 RFC 6749 上では Role(役割)として定義されています。 設計議論をするときは、左端の「通称」を使って話しておけば伝わるはずです。
| 通称 | OAuth 用語 | OIDC 用語 | 説明 |
|---|---|---|---|
| ユーザー | Resource Owner / End User | User / Person | アプリの利用者。ユーザーリソースを使いたい主体なので、OAuth では Resource Owner と呼んでいます。 |
| リソースサーバー | Resource Server | Resource Server / UserInfo Endpoint | ユーザーのリソースを保持しているサーバー。アクセスには IdP が発行するアクセストークン※1が必要。OIDC ではユーザー情報を提供するためのエンドポイントも定義しています(こちらは ID トークンを使う)。 |
| クライアント | Client | Relying Party (RP) / Client | アクセストークンを使ってユーザーのリソースにアクセスするアプリ。クラウド上で動作するロジックでも、クライアントと呼ぶのでお気をつけください。あくまで、リソースサーバーに対してのクライアントということを表現しています。 |
| IdP (Identity Provider) | Authorization Server / 認可サーバー | OpenID Provider (OP) / IdP | ユーザー認証機能の提供や、アクセストークン※1の発行を行うサーバー。実体(エンティティ)は、リソースサーバーと同じでもよい。 |
※1 … 実際には複数のトークンがありますが、ここでの説明では代表的なトークンである「アクセストークン」という単語でまとめています。
OAuth による認可のフローの種類
認可のフローというのは、クライアント(アプリ)が IdP からアクセストークンを取得するまでの手順のことです。 OAuth 2.0 (RFC 6749) では、下記の 4 つの認可フローを定義しています。 一番重要かつ理解しておきたいのは、1 つ目の 認可コード を使うフローです。
| 認可フローの種類 | 説明 |
|---|---|
| 認可コード (Authorization Code Grant) | IdP が生成したランダムな「認可コード」を使って各種トークンを取得するフローです。クライアント(アプリ)はブラウザからのリダイレクトによって認可コードを受け取り、それを使ってアクセストークンを取得します。OAuth の中で最も一般的なフローです。 |
| インプリシット (Implicit Grant) | 認可エンドポイントが(認可コードではなく)アクセストークン自体を発行するフローです。リダイレクト先の URL にアクセストークンが付加されてしまう(クライアントではなくユーザーにも見えてしまう)というセキュリティ上の懸念があり、現在は非推奨 (deprecated) となっています。 |
| リソースオーナー・パスワード・クレデンシャルズ (Resource Owner Password Credentials Grant) | ユーザー (Resource Owner) が、自らの認証情報(ユーザー名・パスワードなど)をクライアントに直接入力し、クライアントがそれをそのまま認可サーバーに送ってトークンを取得するフローです。第三者のクライアント(アプリ)にパスワードが渡されてしまうという問題があり、現在は非推奨 (deprecated) となっています。 |
| クライアント・クレデンシャルズ (Client Credentials Grant) | クライアント(アプリ)が自らのクレデンシャル(例:client_id + client_secret、あるいは API キーなど)を使って、自分自身の名義でアクセストークンを取得するフローです。ユーザー (Resource Owner) の認証・同意は介さず、トークンは「ユーザーの権限」ではなく「クライアント自体の権限」を表します。主にバックエンドサーバー間での連携に使われます。 |
認可コードフロー (Authorization Code Flow)
認可コードフロー (Authorization Code Flow) は、OAuth 2.0 における最も一般的な認可フローなので、最低限この仕組みさえ押さえておけば、「OAuth 知ってるよ」と言っていいと思います。 ポイントは、クライアント(アプリ)がブラウザ経由で一時的な 認可コード を受け取って、そのコードを使って最終的なアクセストークンを取得するところです。
このような一見まわりくどい構成にすることで、アクセストークンの取得処理がクライアント(アプリ)側に閉じる ため、ブラウザの URL などからトークンが漏れてしまうといったリスクを避けることができます。 つまり、アクセストークンはユーザーにすら見えず、アプリだけが取得できます。 また、認証処理は IdP 側で完結するため、ユーザーのパスワードをクライアント(アプリ)に渡す必要がありません。 よく考えられていますね。
クライアントの信頼性確認
Authorization Server (IdP) は、アクセスしてくるクライアント(アプリ)が正規のクライアントかどうかを検証した上で、アクセストークンを発行する必要があります。 さもないと、悪意のあるクライアントがアクセストークンを取得してしまう可能性があります。 例えば、正規のクライアントのリダイレクト URL を偽装して認可コードを取得し、IdP からアクセストークンを取得する、といった攻撃が考えられます。
クライアントの種類
前述の認可コードフローの図では、クライアント(アプリ)を 1 つの青色の箱でシンプルに表現していますが、実際には、サーバー上で動くアプリであったり、ブラウザ上(クライアントサイド JavaScript)で動くアプリであったりします。 OAuth 2.0 では、クライアント(アプリ)を「資格情報を安全に保持できるか」で次の 2 種類に分けており、それぞれクライアントの信頼性の検証方法が異なります。
- コンフィデンシャル・クライアント (Confidential Client)
- 定義:クライアント・シークレットを安全に保持できるクライアント。 一般的にはクラウド上で動作するアプリケーションです。
- 例:サーバーサイドアプリ、BFF (Backend For Frontend)
- 特徴:クライアント・シークレットなどで「自分が正当なクライアントだ」と証明でき、IdP 側は容易にクライアントを検証できます。
- パブリック・クライアント (Public Client)
- 定義:クライアント・シークレットを安全に保持できないクライアント。 一般的には端末側で動作するアプリケーションです。
- 例:ユーザーエージェントベースのアプリ(典型的にはブラウザ上で動くクライアントサイド JS)、ネイティブアプリ(Android/iOS アプリやデスクトップアプリ)
- 特徴:アプリ内に安全にクライアント・シークレットを埋め込むことが困難なため、IdP は送られてきたクライアント・シークレットを信頼できません。 パブリック・クライアントでは、PKCE (Proof Key for Code Exchange) などの OAuth 2.0 拡張を組み合わせて使うことで、認可コードのなりすまし利用を防ぎます。
コンフィデンシャル・クライアントの検証方法
サーバーサイドで動作するコンフィデンシャル・クライアントであれば、環境変数などに秘密の クライアント・シークレット を登録しておくことができるので、IdP との通信時にそれを送るだけで自分が正規のクライアントであることを証明できます。
具体的には、HTTP リクエストボディに client_id と client_secret を含めて送信する方法や、HTTP ヘッダに Basic 認証の仕組みで client_id と client_secret を含めて送信する方法があります。
クライアント ID やクライアント・シークレットは、IdP の Web フォーム上でクライアントを登録するときに発行されるのが一般的です。
IdP は、コンフィデンシャル・クライアントに対しては安心してアクセストークンを発行することができます。
パブリック・クライアントの検証方法
一方、クライアントサイド JavaScript のような端末側で動作するパブリック・クライアントでは、クライアント・シークレットを使って自分が正規のクライアントであることを証明することができません。 なぜなら、クライアント・シークレットを JavaScript コードに埋め込んでしまうと、ブラウザの開発者ツールなどで簡単に見えてしまい、悪意のあるクライアントからも使われてしまうからです。 認可コードフローの途中で、何らかの形で「認可コード」が悪意のあるクライアントに傍受されてしまうと、それはすなわちアクセストークンを取得されてしまうことを意味します。
そこで考えられた方法が、PKCE (Proof Key for Code Exchange)(ピクシー)という OAuth 2.0 の拡張仕様です。
PKCE では、クライアントが認可コードを取得する前に、ランダムな文字列 code_verifier を生成し、そのハッシュ値である code_challenge を認可リクエストに含めて送信することで、認可コードを取得したクライアントが正規のクライアントであることを検証できるようにしています。
OIDC (OpenID Connect)
…
その他/補足情報
現在の推奨構成
ブラウザベースのアプリ(SPA)やモバイルアプリでは、現在では「認可コードフロー + PKCE(ピクシー)」の構成が推奨されます。
- 認可コード: アクセストークンをブラウザに露出させずにクライアントに渡す。
- PKCE: シークレットを持てないクライアントでも、code_verifier / code_challenge を使うことで認可コードのなりすまし使用を防ぐ。
代表的な IdP
- Keycloak … オープンソースの IdP
- Amazon Cognito … AWS で提供されている IDaaS
- Okta … 商用 IDaaS(企業向け)
- Auth0 … 商用 IDaaS(開発者向け)(2021 年に Okta が買収)
- Authlete … IdP を実装するための API を提供
OAuth 2.0 の拡張
- PKCE (Proof Key for Code Exchange) (発音:ピクシー)
- RFC 7636: Proof Key for Code Exchange by OAuth Public Clients(2015 年)
- PKCE は、モバイルアプリや SPA など「クライアントシークレットを安全に保持できないパブリック・クライアント」において、認可コードが途中で傍受されても攻撃者(悪意のあるクライアント)がトークンを取得できないようにするための仕組みです。
- クライアントが
code_verifierとそのハッシュであるcode_challengeを生成し、認可リクエストとトークンリクエストでそれぞれ送ることで、認可サーバーが「同じクライアントからの正当なリクエストか」を検証できるようにします。
- Device Flow(デバイスフロー)
- RFC 8628: OAuth 2.0 Device Authorization Grant(2019 年)
- テレビなどの入力手段に乏しいデバイスのための OAuth 2.0 拡張仕様です。
- ユーザーはテレビの UI 上でサインイン用のパスワードを入力する代わりに、スマートフォンや PC など別のデバイスで認証を行い、テレビ上のアプリにアクセストークンを取得させることができます。