OAuth 2.0
Nogiku SSO は OpenID Connect(オープンアイディー・コネクト)という枠組みによって実現されています。この OpenID Connect を理解するために、まずは OpenID Connect の親とも言える OAuth 2.0 について学んでいきます。
OAuth(オーオース)は、ユーザーが第三者のアプリケーションに自分の代わりに限定的なアクセス権を与えるために業界で広く使われている「認可」の枠組みです。
身近な例としては「写真加工アプリを使いたいとして、あなたの許可を得て、クラウドストレージ上の特定のフォルダだけ読み取りを許可する」ような場合が挙げられます。こうしたアクセス権限の委譲を、統一規格・プロトコルとして定義したものが OAuth 2.0 になります。
OAuth 2.0 の背景
ここからは、OAuth 2.0 がどのような背景で必要とされるのか、登場人物を説明しながら順にたどっていきます。
(1)リソースサーバーとデータ
まず前提として、あるサービスには リソースサーバー(Resource Server) があり、その内部にユーザーのデータが保管されているとします。上の写真加工アプリの例においては、クラウドストレージサービスがこれに該当します。
このときデータの所有者のユーザーのことを リソースオーナー(Resource Owner) と言います。
(2)データを利用したいアプリがある
そのデータを使いたい クライアント(Client) 、つまりクライアントアプリケーションがあります。例えば、写真加工アプリがクラウドストレージ上の画像を読み取りたい、といったケースです。
(3)クライアントとリソースサーバーのやりとり
クライアントは HTTP リクエストを通じて、リソースサーバーに直接データを要求します。
(4)正規のリクエスト
この状態で、クライアントがリソースサーバーにユーザーのデータを要求すると、リソースサーバーはデータを返します。
(5)悪意のあるアプリによるリクエスト
ここが重要です。上の仕組みのままだと、悪意のあるアプリ もリソースサーバーに同じようにリクエストを送るだけで、ユーザーのデータを取得できてしまいます。
これは、誰がリクエストしているのか、リソースオーナーが許可しているのかを区別できる仕組みになっていないためですね。
そこで、リソースサーバーに何らかの保護をかけて、悪意のあるアプリに勝手にデータを取得されないようにしましょう。
(6)アクセストークンによる保護
OAuth 2.0 では、リソースサーバーを守るための手段として アクセストークン(Access Token) を使います。
アクセストークンは、「このクライアントアプリケーションが、ユーザーの代わりに特定のデータを利用することを許可されている」ことを示す 認可の証明書 です。
リソースサーバーは、リクエストに添付されたアクセストークンを取り出し、そのトークンに必要な権限があるかを調べてから、初めてデータを返します。
(7)トークンを発行する係が必要
この仕組みを機能させるには、あらかじめクライアントにアクセストークンを渡しておく必要があります。必然的に、アクセストークンを発行する係が必要になります。
OAuth 2.0 においてこの役割を担うのが、 認可サーバー(Authorization Server) です。
OAuth 2.0 の登場人物
ここまでの流れを整理すると、OAuth 2.0 には主に次の 4 つの登場人物が登場します。
| 用語 | 役割 |
|---|---|
| リソースオーナー(Resource Owner) | データの所有者。アクセスを許可するかどうかを決める主体(多くの場合、エンドユーザー) |
| クライアント(Client) | リソースオーナーの代わりにリソースサーバーへアクセスを要求するアプリケーション |
| 認可サーバー(Authorization Server) | リソースオーナーの許可を得たうえでアクセストークンを発行するサーバー |
| リソースサーバー(Resource Server) | アクセストークンを検証し、保護されたデータを提供するサーバー |
Nogiku における認可サーバーとリソースサーバー
Nogiku において、認可サーバーは apps/auth が担っています。また、リソースサーバーの一例として apps/api が挙げられます。
リソースオーナーの同意
ここまでの説明ではアクセストークンは認可サーバーが発行するんだ、というところまでを簡単に説明しました。
しかし、実際のフローではどのようにして安全にアクセストークンを発行するかが重要になります。なぜなら、せっかく発行したアクセストークンも途中で攻撃者によって盗まれてしまうと台無しだからです。
- クライアントが認可サーバーにアクセストークン(の発行)を要求する
- 認可サーバーは、クライアントが要求している権限をリソースオーナーに提示する
- このアプリがあなたの情報にアクセスすることを許可しますか?のような画面を見たことはありませんか?あれです。
- リソースオーナーが許可すれば、認可サーバーがアクセストークンを発行する
アクセストークンの要求と応答のやりとりを標準化したものが OAuth 2.0です。
RFC 技術仕様
OAuth は世界中で統一された仕様です。IETF という機関が技術仕様を策定しており、OAuth 2.0 については RFC 6749 に決まっています。すべてを解読できる必要はありませんが、こういうふうに統一規格が定められているんだ、というのは知っておいてもよいでしょう。
この IETF による統一された技術仕様があるので、世界中で OAuth 2.0 といえば同じものを指し、各サービスごとの仕様差分をあまり意識することなく連携の実装ができるようになっているわけです。
OAuth 2.0 の認可フロー(Grant Type)
OAuth 2.0 では、アクセストークンを取得する手順(認可フロー / Grant Type と呼ばれます)が複数定められています。用途やクライアントの種類によって、使用する認可フローを変えています。
| 認可フロー | 概要 | 備考 |
|---|---|---|
| Authorization Code Flow | ブラウザ経由で認可コードを取得し、サーバー側でトークンに交換する | 最も安全 |
| Client Credentials Flow | ユーザー不在の、サービス間通信向け | 機械同士の認可 |
| Device Code Flow | 入力手段が限られたデバイス(TV 等)向け | 別デバイスでの認可 |
| Implicit Flow | ブラウザに直接トークンを返す(旧方式) | 非推奨 |
| Resource Owner Password Credentials | ユーザー名・パスワードをクライアントが直接送る(旧方式) | 非推奨 |
推奨される認可フロー
現在のベストプラクティス(OAuth 2.1 を含む)では、Authorization Code Flow(認可コードフロー) が最も安全で推奨される手段です。さらに、PKCE (後述します)を併用することで、より安全に利用できます。
Implicit Flow や Resource Owner Password Credentials Flow は、設計上の問題から新規利用は非推奨とされており、OAuth 2.1 では仕様から削除される方向性で検討が進んでいます。
Authorization Code Flow
Authorization Code Flow は、大きく分けて 認可フェーズ(リソースオーナーがログイン・同意する)と トークン取得フェーズ(サーバー側でトークンに交換する)の 2 段階で構成されます。
全体の流れ
下の図が理解できるように、順に説明します。
学習にあたって
Authorization Code Flow による認可の流れは、初見ではとても複雑に感じる人が多いと思います。
最初からすべてを理解しようとせずに、一度ざっと全体像を把握した後は、手を動かして何度かフローを手書きしてみることをおすすめします。やっている間に意外と頭に入ってくると思います。
実はこの後でもう少し追加仕様(OpenID Connect, PKCE)が加わってもう少しだけ複雑になるので、一度この資料を最後まで読んでからやってみてください。
① 認可フェーズ:認可コードを取得する
リソースオーナーがクライアントを使い始めると、クライアントはブラウザ(ユーザーエージェント)を 認可サーバー の認可エンドポイントへリダイレクトします。
この段階でブラウザに渡されるのは 認可コード(Authorization Code) であり、アクセストークンそのものではありません。認可コードは短命で、1 回限り使える使い捨てのコードです。
なぜトークンを直接渡さないのか
ブラウザの URL や履歴、Referer ヘッダーなどにアクセストークンが残ると漏洩リスクが高まります。Authorization Code Flow では、まず短命の認可コードだけをブラウザ経由で受け取り、クライアントアプリケーションのサーバー側で安全にトークンへ交換します。
主なクエリパラメータは次のとおりです。
| パラメータ | 説明 |
|---|---|
client_id | クライアントを識別する ID |
redirect_uri | 認可後に戻る URL |
response_type | code を指定(Authorization Code Flow) |
scope | 要求する権限の範囲(例: openid profile email) |
state | CSRF (後述)対策用のランダム文字列 |
② トークン取得フェーズ:認可コードをトークンに交換する
クライアントの バックエンド が、受け取った認可コードを使って認可サーバーの トークンエンドポイント にリクエストを送り、アクセストークンを取得します。
この通信は サーバーサイド で行われるため、クライアントシークレット(client_secret)を安全に使えます。confidential client(サーバーサイドを持ち、サーバー側に秘密情報を保持できるクライアント)向けの標準的なやりとりです。
③ リソースアクセス:データを取得する
取得したアクセストークンを使い、クライアントはリソースサーバーにデータを要求します。
Confidential Client と Public Client
ここまでの Authorization Code Flow では、トークン取得時に client_secret を送る前提で説明しました。しかし、すべてのクライアントが client_secret を安全に持てるわけではありません。
OAuth 2.0 では、クライアントを大きく次の 2 種類に分類します。
| 種類 | 特徴 | 典型例 |
|---|---|---|
| Confidential Client | サーバーサイドを持ち、第三者に漏洩しない形で秘密情報を保持できる | Web アプリのバックエンド、サーバー間連携 |
| Public Client | ソースや端末上に配置されるため、秘密情報を隠せない | SPA(ブラウザのみ)、モバイルアプリ、デスクトップアプリ |
client_secret を安全に持つことが出来るものを Confidential Client、そうでないものを Public Client と呼んでいるわけです。
Confidential Client
Confidential Client は、認可コードのトークン交換をサーバー側で行います。client_secret はサーバー上の環境変数などとして置き、ブラウザやエンドユーザーの端末に露出することはありません。
認可サーバーはトークン発行時に次を確認します。
- 認可コードが正しいこと
- そのコードを提示しているのが、登録済みのクライアント本人であること(
client_id+client_secret)
そのため、仮に認可コードがブラウザ経由で漏洩しても、攻撃者が client_secret をも持っていない限りトークン交換は失敗します。
Public Client
フロントエンドしか持たない静的 Web アプリの場合、 client_secret を安全に持つことができません。JavaScript のコードや、ブラウザ・端末のストレージなどはすべてクライアントに露出し、解析・抽出が可能なので、原理的に「秘密の値を持つ」ということ自体ができないのです。
したがって、このようなクライアントは Public Client と呼ばれ、トークン交換時に client_secret によるクライアント認証を前提にできません。認可コードが漏洩した場合、そのコードだけでトークン交換を試みられるリスクが相対的に高くなります。
PKCE
Public Client では client_secret による防御が使えないため、代わりに PKCE(Proof Key for Code Exchange / ピクシー)を使います。
PKCE では、認可開始時にクライアントだけが知る一時的な秘密(code_verifier)を用意し、そのハッシュを認可リクエストに載せます。トークン交換時には元の code_verifier を提示し、認可サーバーが照合します。認可コードだけを盗んだ攻撃者は、verifier が無いためトークンを取得できません。
現在のベストプラクティスでは、Public Client だけでなく Confidential Client でも PKCE の併用が推奨されます。仕組みの詳細と、認可コード横取りへの防御としての位置づけは OAuth / OIDC フローの防御 で扱います。