OAuthとOIDCの前にJWTから勉強しよう
はじめに
認証や認可の実現方法は、システム開発における頻出の関心事の一つかと思います。そんな中、JSON Web Token(JWT)/OAuth2.0/Open ID Connect(OIDC)という言葉もよく耳にするところです。
しかし、「JWTって結局どう使うの?」「OAuth2.0やOIDCってJWTとどう関係するの?」「OAuth2.0とOIDCの違いって何?」という疑問を持つ方も多いのではないでしょうか。
また、JWTに「署名」や「検証」といったキーワードが絡んでくると、途端にハードルが上がったように感じるものです。
これらのキーワードについては既に沢山の記事が公開されていますが、本記事では以下に焦点を当てて解説していこうと思います。
- JWT/OAuth2.0/OIDCの関係性を明らかにする
- OAuth2.0/OIDCより先にJWTを理解することで混乱を防ぐ
- JWTをシステム開発の実務で利用するときの注意点を示す
関係性の理解編
認証と認可のちがい
まず、「認証(Authentication)」と「認可(Authorization)」の違いをおさらいしておきましょう。
-
認証
- 利用者が「誰であるか」を確認すること
-
認可
- 利用者が「その操作を許可されているか」を確認すること
OAuth2.0のざっくり解説
OAuth2.0は、 リソースへのアクセス権限(認可) を委譲するためのプロトコルです。ユーザーの資格情報を直接渡すことなく、第三者アプリケーションにリソースへのアクセスを許可する仕組みを提供します。
OAuth2.0では次の2種類のトークンが登場します。
-
アクセストークン
- APIにアクセスするために必要なデータ(有効期間は短い)
-
リフレッシュトークン
- アクセストークンを更新するためのデータ(有効期間は長い)
OIDCのざっくり解説
OIDCは、OAuth2.0をベースにした認証用のプロトコルです。OAuth2.0が認可のみを扱うのに対し、OIDCは認証情報の提供にも対応します。
OIDCでは、アクセストークンとリフレッシュトークンに加えて、新たに IDトークン が登場します。
-
IDトークン
- ユーザーが認証済であることを示すトークン
JWTのざっくり解説
JWTはJSON形式でトークンを実現するためのデータ構造です。多くの場合、JWTの関連仕様である JSON Web Signature(JWS) と組み合わせて使用されます。JWSはJSON形式のデータに署名する手続き方法を定めた仕様であり、JWTとJWSと組み合わせることで、署名付きのトークンを実現することができます。
JWT+JWSは主にWebサービスでの情報の受け渡しに使われ「トークンが改ざんされていないこと」を証明できることが特徴です。
そして、JWTの最大の利点は、電子署名の仕組みを使うことでトークンの正当性を発行元に問い合わせることなく検証できるという点にあります。発行元に問い合わせずに済むことで、サーバーへの負荷を減らしつつ、トークン発行側とトークン検証側を疎結合に保つことができます。
JWTが登場すると混乱する理由
OAuth2.0やOIDCでは、認証や認可の状態を示すために、各種のトークンというデータ構造を用いていることがわかりました。これだけであれば難しい話ではありません。
しかし、ここにJWTが登場すると混乱してしまう理由はなぜでしょうか?私は、以下が混乱の理由だと考えています。
理由1)OAuth2.0/OIDCでトークン実装の扱いに差がある
OAuth2.0/OIDCでは、それぞれの仕様においてJWTやJWSの扱いに以下のような差があります。
- OAuth2.0
- アクセストークンやリフレッシュトークンの形式は仕様として定められていない
- OIDC
- IDトークンはJWSで署名されなければならない(MUST) ことが仕様として定められている
ここで混乱しやすいのは、OAuth2.0でJWTを扱う必然性はありませんが、実際には多くの実装でJWT形式が採用されているという点です。その結果、OAuth2.0について調査をしても「JWTを使うパターン」と「JWTを使わないパターン」の情報がインターネット上に双方存在するため、OAuth2.0とJWTの関係性が曖昧になってしまうのではないかと考えています。
理由2)依存の向きを間違えやすい
JWTはOAuth2.0/OIDCのトークンとして登場することが多いデータ構造です。そのため、JWTがOAuth2.0/OIDCの仕様に依存したデータ構造だと誤解してしまうケースがあるようです。
しかし、JWTは「認証方式」や「認可方式」そのものではなく、単独で仕様が策定されているデータ構造です。そのため、前述の関係性は逆で、以下が正しい解釈です。
- OAuth2.0が(実装として)JWTを採用する(ことがある)
- OIDCが(仕様として)JWSに依存する
理由3)JWTには署名・検証の仕組みがある
前述のとおり、JWT+JWSは署名付きの構造化データです。そのため、署名・検証を行うために一定の処理手順が必要になります。
しかし、直感的にはJWTは単なるトークン(文字列)といったイメージが付きまとうため、JWSによる署名・検証の仕組みに意識が至らず、理解が進まないのではないでしょうか。
何から理解すればよいか?
ここまでに示した関係性のイメージ図が以下になります。
イメージ図から伝わると嬉しいのですが、「左右対称に見えてちょっと違う」ことがわかります。この「ちょっと違う」が混乱のもとになるわけです。では、混乱せずに理解を進めていくにはどうすればいいでしょうか?
その答えは、OAuth2.0/OIDCが依存するJWT+JWSから理解を進めることです。
JWTとOAuth2.0/OIDCは切り分けて考える必要があるということを出発点に、JWTの取扱いや注意点をまず理解しましょう。その後に、OAuth2.0やOIDCの中で「JWTがどのように使われているのか」を理解することで、各技術の役割や関係性が明確になり、混乱せずに整理できると思います。
それでは、以降ではJWTの取扱いについて解説していきます。
JWT理解編
JWTの構造
JWTの構造は本当に多数の記事で言及されていることなので、詳細は他記事に譲りますが、JWTはヘッダ・ペイロード・署名の3つで構成されることをここでは押さえておきましょう。
ヘッダ
JWTのヘッダには、署名アルゴリズムなどのメタデータが格納されています。
{
"alg": "HS256",
"typ": "JWT"
}
ペイロード
JWTのペイロードには、認証されたユーザのユーザ情報等が格納されています。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
署名
JWTには、改ざんされていないことを保証するための署名が付与されます。署名は、以下の手順で生成されます。
- ヘッダのJSON文字列をBase64URLエンコードする。
- ペイロードのJSON文字列をBase64URLエンコードする。
- 1と2で生成した文字列をを半角ピリオドで連結する。
- 3の連結文字列から生成したハッシュ値に対し、指定の署名アルゴリズムと秘密鍵を使って署名する。
全体構成
JWTの全体構成は次のようになります。
<base64url(header)>.<base64url(payload)>.<base64url(signature)>
JWTを検証するってどういうこと?
JWTの「検証(verification)」とは、あるトークンが正当であることを確認するプロセスです。
主に以下の観点で確認が行われます。
- 形式が正しいか
- 信頼できる発行者によって生成され、かつ、改ざんされていないか(署名の検証)
- 有効期限や発行者情報が正しいか
- 利用者に適切なスコープが与えられているか
以降では、特に重要な署名の検証について解説していきます。
署名検証の流れ
署名検証は、JWTのヘッダとペイロードを基に署名を再計算し、トークンに含まれる署名と一致するかどうかを確認する処理です。大まかな流れは以下のとおりです。
- JWTを3つのパート(ヘッダ、ペイロード、署名)に分割する。
- ヘッダとペイロードをBase64URLデコードし、署名アルゴリズムや
kid
(Key ID)などの情報を取得する。 - 署名をBase64URLデコードし、バイナリ化する。
- JWTから
<base64url(header)>.<base64url(payload)>
を取り出し、ハッシュ化する。 - 3の署名を
kid
に対応する公開鍵で変換し、その値が4のハッシュ値と一致するかを確認する。
署名検証に使用する公開鍵について
JWTの署名を検証するためには、署名に使用された秘密鍵に対応する公開鍵が必要です。この公開鍵は、トークンの発行元が提供するのが一般的で、多くの場合 JWKS(JSON Web Key Set)形式 で公開されます。
各種の認証・認可サービスなど、トークンの発行元の多くは公開鍵を取得するための軽量なエンドポイント(URL例: https://example.com/.well-known/jwks.json
)を提供しています。
ここで重要なのは、公開鍵は運用の都合によりローテーション(変更)される可能性があるという点です。トークン発行元が何らかの理由で公開鍵を変更した場合に、検証側のシステムが古い公開鍵を使い続けるとすべての署名検証が失敗し、結果としてシステムがダウンする可能性があります。
このリスクを防ぐためには、以下のような処理を実装する必要があります。
- 公開鍵の変更を検知する処理
- JWKSの内容を取得する処理
- JWTのヘッダに含まれる
kid
(Key ID) を使って 適切な公開鍵を選択する処理 - 新しい公開鍵を保存(キャッシュまたは永続化)する処理
ここで、kid
はJWTのヘッダに含まれる識別子で、以下の役割を果たします。
- 発行者が複数の公開鍵を提供している場合でも、署名に使われた鍵を一意に特定する。
- クライアント側で
kid
をキーにJWKSから公開鍵を選択することで、検証を効率化する。
このように、公開鍵のローテーションに対応できるようにしておくことは、JWTを用いたシステムの可用性・信頼性を保つ上で極めて重要です。
便利なJWT、でもちょっと待って
前述のとおり、JWTの最大の利点は「発行元に問い合わせることなくトークンの正当性を確認できること」にあります。また、JWTのペイロードには有効期限などの情報が含まれており、サーバサイドでトークンと属性情報のマッピングを保持する必要がないというステートレス性も魅力です。
このように、JWTは非常に便利な仕組みですが、利用にあたっては慎重な設計と運用が求められる側面もあります。
JWTに「何を入れるか」は慎重に
JWTのペイロードには、必要に応じて独自の情報(カスタムクレーム)を追加できますが、カスタムクレームに格納するデータによっては、情報漏洩のリスクが高まります。
JWTはクライアント側で参照可能な情報ですが、仮にクライアント側のセキュリティに不備がある場合、情報漏洩のリスクがあります。もし、カスタムクレームに格納するデータが機密性の高い情報である場合、漏洩の被害が甚大化する恐れがあります。
JWTは複数のシステム間で持ちまわるトークンであるため、システム間連携のために持ちまわりたいデータをカスタムクレームに格納するといった処理方式が可能ですが、このような処理方式はJWTの本来の目的から逸脱しますし、本来クライアントが参照すべきでないデータが露出する恐れがあるため、避けた方がよいでしょう。
ログアウトとJWTの即時無効化
JWTはステートレスな仕組みのため、トークンが一度発行されると、有効期限まではそのトークンは有効とみなされるという特性があります。そのため、ユーザーのログアウト等でトークンの正当性が失われた後も、有効期限内であればJWTがそのまま使えてしまう、というリスクが生まれます。
この課題に対処するための方式の一つとして、ブラックリスト方式が挙げられます。
ブラックリスト方式では、JWTの識別子(jti
など)をログアウト時にサーバサイドのブラックリストに登録し、以後の検証で無効と判定する方法です。サーバはリクエストのたびにブラックリストを参照して無効トークンを排除します。
おわりに
最後に大事なポイントをまとめます。
- JWT/OAuth2.0/OIDCの関係性を正しく理解することが重要
- JWTは「トークンの形式」であって「認証・認可の手段」ではない
- JWTは便利で柔軟な仕組みだが、正しく使わないとリスクが高まる
こうしたポイントを押さえながら、JWTを安全に活用していきましょう。

NTT DATA公式アカウントです。 技術を愛するNTT DATAの技術者が、気軽に楽しく発信していきます。 当社のサービスなどについてのお問い合わせは、 お問い合わせフォーム nttdata.com/jp/ja/contact-us/ へお願いします。
Discussion