2020年版 チーム内勉強会資料その1 : JSON Web Token

おはようございます。ritou です。

5月下旬ぐらいにチーム内勉強会としてJSON Web Token(JWT)についてわいわいやりました。 その際に作成した資料に簡単な説明を添えつつ紹介します。 このブログではJWTについて色々と記事を書いてきましたが、その範囲を超えるものではありません。

ちょっとだけ長いですが、ちょっとだけです。お付き合いください。それでは始めましょう。

JSON Web Token boot camp 2020

JWTBootCamp2020 001

JWTBootCamp2020 002

今回の勉強会では、JWTについて概要、仕様紹介という基本的なところから、業務で使っていくにあたって気をつけるべき点といったあたりまでカバーできると良いなと思っています。

JSON Web Token 概要

JWTBootCamp2020 003

まずは概要から紹介していきます。

JWTBootCamp2020 004

JSON Web Tokenの定義とはということで、RFC7519のAbstractの文章を引用します。

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties.

JSON Web Token (JWT) は、2者間で転送されるクレームを表現するためのコンパクトで URLSafeな方法です。

補足します。

JWTBootCamp2020 005

JWTはとにかく色々なデータ、例えば構造化されたものからバイナリデータまでを複数のサービス、システム間でやりとりするため、URLセーフな文字列にエンコードする仕組みです。 また、そのエンコード結果の文字列自体をJWTと呼ぶこともあります。

さらに、JSON Web Signatureという署名をつける仕組みを利用することで改ざん検知が可能になります。JSON Web Encryptionという暗号化を施すことにより、センシティブなデータのやりとりが可能になります。 この2つのうち、よく使われているのを見かけるのがJSON Web Signatureの方でしょう。 ということでここからはJSON Web Signatureについての解説を行います。 (ここからの文中でJWTと書いているものは署名つきのJWSを含みます。)

JWTBootCamp2020 006

そもそも今回、なぜJWTを取り上げたのかにも触れておきます。 誕生のきっかけということで、JWTはOpenIDファウンデーションのWGで行われたID連携やソーシャルログインを実現するための仕組みであるOpenID Connectの仕様策定に合わせ、IETFのJOSE(読みはホゼ) WGにて仕様策定が開始されました。

GoogleAppleでサインインの裏側で動いているOpenID Connectの中で、ID Tokenというユーザーの属性情報や認証時の情報などをやり取りする際にJWTが利用されています。 もともとSAMLというXMLベースのID連携の仕組みで使われていたXML署名という仕組みがあらゆるユースケースに対応できるようにした結果複雑になってしまったという経緯から、JWTはより容易に実装できてコンパクトに表現できるセキュリティトークンを目指しました。 私はこのOpenID Connectの仕様策定にContributorとして参加していたため、この後説明しますが2015年に複数のRFCになるまでの経緯を横目でチラ見していて最近のJWT単独の普及についても注目しております。 ということで今回、ネタとして取り上げました。

JWTBootCamp2020 007

ここからはJWTのユースケースをいくつか見ていきましょう。 サービス、システム間のやりとりに使うという点を意識して、まずはJWTの発行者、そして受信者(利用者)という観点で同一か別かで整理していきます。

JWTBootCamp2020 008

発行者、受信者が同一のユースケースとして、WebアプリケーションにおけるセッションCookieへのJWT適用があります。 (厳密にはmonorithなサービスであれば発行者、受信者が同一となりやすいしょうけれども、規模が大きくなれば発行者、受信者が別になるケースもあり得るでしょう。)

以前からHTTP Cookieにセッション情報を詰め込むという、Cookie Storeとか呼ばれてきたものがあり、以前から様々なWebApplicationFrameworkにてオレオレ署名つきエンコーディングなどで実装されていることはご存知の方が多いかもしれません。これにJWTを利用することができるでしょうというお話です。 さらに、セッションIDをHTTP Cookieに持ってDBなりでセッション情報を管理する実装についてもJWTを適用可能です。セッションIDと発行者、発行日時、有効期限などを合わせて署名つきのJWTに含むことで、ブラウザ上で手元にあるHTTP Cookieの内容をいじって挙動を調べてみるようなことを困難にできます。 処理の流れとしては、Webサーバーがセッション情報に更新があるたびにHTTP Responseとして発行されたものをブラウザがハンドリングし、次のHTTPRequestにてWebサーバーに送ります。

JWTBootCamp2020 009

さらに、WebアプリケーションのHTML Formを用いたPOSTリクエストなどで利用されるCSRF対策トークンにもJWTを適用できます。 この例の方が発行者、受信者が同一である感じが出そうですね。

CSRF対策トークンで重要なのはセッションとの紐付けです。セッションIDそのものではなく鍵を用いて生成したハッシュ値などを格納しておくのが良いでしょう。

JWTBootCamp2020 010

発行者、受信者が別であるユースケースとしては、WebAPIを利用する際の認可用トークンがあります。OAuthのアクセストークンで、発行者であるユーザー認証やアクセス管理を司るサーバー、受信者であるサービスを提供するサーバーが分かれている場合を想像してください。

JWTBootCamp2020 011

よりシンプルなユースケースとして、

  • 署名つきのAPIリクエストにJWTを適用
  • 外部サービスにユーザー情報などを渡す際に署名つきJWTを利用

というものがあります。

JWTBootCamp2020 012

ここまでゆるゆるとユースケースを紹介してきましたが、一旦署名つきのJWTを利用するメリット、デメリットを整理します。 柔軟なデータを文字列にしてやりとり可能なところ、署名とメタデータを組み合わせることで、発行者、受信者、有効期限の検証が可能となる点はとても魅力的だと思います。 一方で、暗号化ではないため、どんな値を含むのかを誰でも知ることができるという点、含まれる情報によるデータサイズの増加には気をつける必要があります。

JWTBootCamp2020 013

JWTが使われている理由として、やはり標準化の強みがあるでしょう。

仕様はRFCになり、ライブラリも各種言語で出揃っています。 OAuth/OpenID Connectをはじめとした標準化プロトコルでの採用実績もありますし、ここまで紹介したように単体で使われるユースケースも増えています。これまで独自の署名つきエンコードを使っていたところからの移行などで利用するケースもあるでしょう。

さらに、最近RFC化されたJWT BCP(Best Current Practice)のように、良くも悪くも枯れてきたと言うか、ここ抑えると安全な実装を実現できると言うのが明確になっている点も様々なところで使われている理由だと思います。

JWTBootCamp2020 014

JWTのユースケースでセッションCookieAPIアクセス時のトークンの例を紹介しました。 このあたりで混乱を呼んでいるものの一つに、Single Page Application のログイン状態の実現方法があります。

この文脈では JWT はエンコードフォーマットだけではなく WebStorage にJWT形式のトークンを保存してAPIアクセスする方式 というような解釈がされがちです。 そのような解釈のまま、HTTP CookieにセッションIDを保存してHTTP Request時に送られるやり方 と比較されたりするんですが、比較の軸が増えてしまって話がまとまりません。

  • セッションIDだけなのか色々詰め込む内包型とするのか
  • 文字列自体に情報を詰め込むのか、ブラウザである程度編集可能なHTTP Cookieの属性値を使うのか

のような比較軸の細かい整理をすることが重要です。

ritou.hatenablog.com

JWTBootCamp2020 015

そして、このCookieとの比較でもよく言及される点として、「JWT=ステートレスでなければならない」「JWTを使う場合は必要な情報を詰め込み、その内容だけで機能を実現させる必要がある」という一種の信仰のようなものがあるのではないかと考えています。

これはとても勿体無いなと感じていて、確かに情報を内包できるという特性は持っていますが、データストアなどを参照するためのキーを持ってはいけないというわけではありません。

例えば単純な識別子を用いたやりとりだったものにJWTを適用するだけで、有効期限や検証という機能を追加できる のです。この考えは既に色々なユースケースで使われているものではありますが、ステートフルなユースケースへも適用できるということを今一度意識してもらえると良いのかなと思っています。

ritou.hatenablog.com

ritou.hatenablog.com

JWTの概要は以上です。

仕様解説

JWTBootCamp2020 016

ここからは、JWTの仕様について簡単に説明していきます。

JWTBootCamp2020 017

JWTに関するコアな仕様が定義されているRFCは5個あります。 その内訳についてですが、

  • 「いつ、誰が、誰に」発行したものか、というようなサービス/システム間のやりとりで必要なメタデータを標準化したもの : RFC7519 JSON Web Token
  • 署名をつけ、検証するために必要なパラメータや実装方法 : RFC7515 JSON Web Signature
  • (今回は紹介しませんが)暗号化周り : RFC7516 JSON Web Encryption
  • 暗号化や署名利用時の鍵の種類、表現 : RFC7517 JSON Web Key
  • アルゴリズム : RFC7518 JSON Web Algorithms

となっています。全てを細かく読んでいくのは時間がかかって大変なので、よく使われている署名つきJWT、JSON Web Signatureについて要点をかいつまんで紹介します。

RFC7515 JSON Web Signature

JWTBootCamp2020 018

JWTBootCamp2020 019

この文字列をご覧ください。皆さんには普通の文字列に見えるかもしれませんが、私にはこう見えます。

JWTBootCamp2020 020

先頭が "eyJ" から始まり、"." で連結されています。

JWTBootCamp2020 021

この文字列の正体としては、RFC7515で定義されている "JWS Compact Serialization" という単一の署名を持つフォーマットであり、世の中で広く使われているのはこのフォーマットです。

仕様ではより複雑なこともできるようになっており、複数の受信者に向けてそれぞれ向けの署名を含むことができる "JWS JSON Serialization" というのも定義されているのですが、使われているのをほとんど見たことがありません。

JWTBootCamp2020 022

改めて先ほどの文字列に戻ります。 最初の eyJ から始まる文字列ですが、Encoded Headerと呼ばれています。

JWTBootCamp2020 023

これはBase64URL EncodeしたJWS Header であり、JWS Header は JSON 形式で表現されます。このJSONの開始タグのあたりで {" から始まる文字列を Base64URL Encodeした時に "eyJ" となるわけです。 内容は自身がどのような種類なのか、署名に関するパラメータを含みます。

JWTBootCamp2020 024

次にPayloadです。2番目の文字列が Encoded Payloadと呼ばれています。

JWTBootCamp2020 025

Base64URL EncodeされたJWS Payloadです。 JWS Headerとは異なり、JWS Payloadはやりとりしたいデータそのものであり、必ずしもJSON形式である必要はありませんが、「誰が」「いつ」「誰に」というような標準的なクレーム(パラメータ)の値が RFC7519 で定義されています。 これと署名検証の仕組みを組み合わせることで「改ざんされていないことが保証できる」文字列となるため、ユースケースはグッと広がります。

JWTBootCamp2020 026

最後にSignatureです。これもEncoded Signatureと呼ばれています。

JWTBootCamp2020 027

Header, Payloadと同様にBase64URL EncodeしたJWS Signatureであり、署名のアルゴリズムはRFC7518、鍵の表現がRFC7517にて定義されています。

JSON Web Signatureの特徴として、署名生成時に利用するBase Stringに対してパラメータのソートなどの正規化が不要であるという点があります。 Twitterなどで使われている OAuth 1.0 という仕組みでは署名つきのリクエストが定義されているのですが、Base Stringを生成するまでの正規化の処理はなかなか面倒です。 JSON Web SignatureではEncoded Header と Encoded Payloadを "." で連結させた値をBaseStringとして利用します。

RFC7519 JSON Web Token

JWTBootCamp2020 028

RFC7519 JSON Web Tokenについて解説します。

JWTBootCamp2020 029

Payloadに含まれるキーバリューの値をJWTクレームと呼びます。

  • JWT自身の識別子である jti
  • host名やサービス内の識別子など、JWT発行者の識別子である iss
  • JWTの受信者、利用者の識別子である aud

という「誰が誰に...」という値が定義されています。

JWTBootCamp2020 030

さらに、「いつ発行され、いつからいつまで有効なのか」を表現するための

  • 発行日時である iat
  • 有効期限 exp
  • この日時以降、有効となる開始日時である nbf

という値が定義されています。

ここまで紹介した値は「汎用的なクレームとして、識別が必要ならば使うと便利だよ。」という意味合いでこのRFC内では基本的にオプショナルです。 JWTを利用するプロトコルや各種プロファイルで「この値を必須として含む」などの定義がされます。

JWTBootCamp2020 031

最初にJWT誕生のきっかけとなったと紹介した「Googleでログイン」を実現するためのプロトコルである「OpenID Connect」でユーザー情報などを伝達するために使われるID TokenはJWTクレームを利用します。

  • issGoogleのアカウント周りを管理するドメイン
  • audGoogleアカウントを受け入れるサービスに client_id として割り当てられた識別子
  • subGoogleアカウントの識別子
  • iss, exp で有効期限を表現

という値がOpenID Connect独自で定義された値と一緒に含まれています。

JWTBootCamp2020 032

ここで紹介した値は全て利用必須なものではなく、これらを利用するプロトコルなどにより決まります。 JWTを扱うライブラリによっては、ここで定義されている値の検証を行うものもあります。 検証の粒度や利用方法については利用するライブラリのドキュメントや実装を追う必要があることに注意は必要でしょう。

RFC7518 JSON Web Algorithms

JWTBootCamp2020 033

RFC7518 JSON Web Algorithmsについて解説します。 ここではJSON Web Signatureの署名生成に利用されるアルゴリズムについて取り上げます。

JWTBootCamp2020 034

一番上にある none については、JWT自体とは別のところで署名生成/検証や暗号化が行われるようなケースで利用します。これではBase64URL Encodeぐらいしかしてないことはここまでの説明でお分りいただけるかと思います。

HSXXX となっているものは、SHA-256, 384, 512といったハッシュ関数を秘密の共有鍵と組み合わせて署名を生成し、同じ計算をして比較することで検証します。 鍵配送が不要な、JWTの発行者、検証者が同一の場合に使いやすいアルゴリズムです。

JWTBootCamp2020 035

RSXXX, PSXXXRSA公開鍵暗号方式ハッシュ関数を組み合わせたデジタル署名を利用するアルゴリズムです。 秘密鍵を用いて署名を生成、公開鍵を用いて署名の検証ができるため、発行者から受信者で公開鍵を渡したり、発行者が公開鍵を提示することで発行者/受信者が別のケースで利用できます。

JWTが使われ始めた頃は、「公開鍵暗号だったら RS256」 という感じで使われていましたが、最近はその状況も変わりつつあります。

JWTBootCamp2020 036

ESXXX公開鍵暗号として楕円曲線暗号を用います。RSAよりも鍵長の短さや処理速度の面で優れているため、最近のプロトコルではこちらを選択するものも見受けられます。

JWTBootCamp2020 037

アルゴリズムと鍵管理の関係は密です。 秘密鍵を公開して後悔しないように気をつけて利用しましょう。

RFC7517 JSON Web Key

JWTBootCamp2020 038

RFC7517 JSON Web Keyについて解説します。

JWTBootCamp2020 039

鍵に関する仕様として、鍵の表現と鍵のセットの表現が定義されています。 アルゴリズムに対応した鍵はどのように表現されるのか、そして鍵のローテーションやアルゴリズム変更時のスムーズな移行を考慮する際に鍵のセットをどう表現するかが重要となります。

JWTBootCamp2020 040

鍵表現のためのパラメータがいくつか定義されています。 このうち重要なのは、鍵の種類である kty, 対応するアルゴリズムである alg, 鍵の識別子である kid です。 他には鍵の利用用途(署名 or 暗号化)である use やさらにその鍵の使い方まで踏み込んだ key_ops があったり、X.509形式の証明書に関連するパラメータがあります。

JWTBootCamp2020 041

HSXXX で利用されるバイナリの秘密鍵kty=octとして表現されます。

JWTBootCamp2020 042

RSXXX で利用されるRSA秘密鍵kty=RSAとして表現されます。長いです。

JWTBootCamp2020 043

公開鍵は少しすっきりと表現されます。alg, kid の値も含まれていますね。

JWTBootCamp2020 044

ESXXX で利用されるECDSAの公開鍵はkty=ECとして表現されます。

JWTBootCamp2020 045

鍵のセットは keys として鍵のリストを持つことで表現されます。 これは先ほども紹介したOpenID ConnectでGoogleから受け取ったJWTの署名検証を行うために公開されている、公開鍵のセットの値です。Appleなども同様に公開しています。

JWTBootCamp2020 046

これらのユースケースとして、有効な鍵情報をjwks_urlとして公開したり、設定ファイルにこの形式で保存するといったものがあります。 弊チームでは設定ファイルなどでPEM形式などを利用していますが、コンパクトに表現できるのであればこのような形式での保存も使っていきたいと思っています。

実装のポイント

JWTBootCamp2020 047

仕様の解説は以上にして、実装のポイントを紹介します。

JWTBootCamp2020 048

ここまで紹介した仕様がRFCになってから5年ぐらいが経ち、良くも悪くも枯れた技術となってきました。 そして最近、RFC8725 JSON Web Token Best Current PracticesとしてJWT実装のベストプラクティスがRFCとなりました。

Qiitaで解説記事を書きましたのでお時間がある方はみてください。

RFC 8725 JSON Web Token Best Current Practices をざっくり解説する - Qiita

JWTBootCamp2020 049

個人ブログにも少し書いたのですが...

JWTBootCamp2020 050

JWTを安全に使っていくためのポイントとして、難しい暗号化処理以外のものとして

  • Payloadに含む情報はよく考えて決める
  • 署名検証処理を確実に
  • 複数のJWTを利用する際は、用途を指定/判別して排他的に検証する

というものが書いてあります。

JWTBootCamp2020 051

Payloadに含む情報について、チーム内の勉強会ではせっかくJWTを使っていてもPayloadに含む情報が足りず、そもそもの目的を満たせていない例を紹介しました(が割愛します)。 これはユースケースに依存するというか、必要な情報が決まるものなのでよく考えましょう。

署名検証については「ちゃんとした」ライブラリを使うことで問題は回避できますが、簡単にこんな攻撃があったよというのを紹介します。

個人的には、最後の用途のあたりが一番重要かと思っているのでスライドのページを割いて紹介します。

JWTBootCamp2020 052

JWTBootCamp2020 053

JWTの署名検証時の脆弱性として有名なのが、alg とSignatureの値を改ざんして検証をパスさせようとするものです。 JWT側の alg に沿って署名検証をしてしまうと、alg=noneを指定してSignature部分を取り去って送られたものを受け入れてしまったり、alg=RS256からHS256に変えられて公開鍵を共有秘密鍵としてハッシュ値をSignatureに指定されたりしたものを受け入れてしまう例が報告されています。

これらを回避するためには、JWTに含まれるalgをそのまま署名検証に使うのではなく、kidに紐付く鍵に紐付くalgを署名検証に利用することが重要です。 その両者が異なる場合はJWTの改ざんが考えられますが、これは署名検証で検知できます。

JWTBootCamp2020 054

ここからはJWTの用途と署名検証について説明します。

ポイントは

  • 用途の表現と指定
  • 鍵の管理
  • 検証

です。

JWTBootCamp2020 055

JWTで用途を表現するために使えるパラメータはいくつかあります。

まずはHeaderのtypです。 例として、Googleが連携済みのサービスにユーザーのセキュリティイベントをJWT形式で通知する仕組みがあって、そこでは secevent+jwt という値を指定しています。 次に、Headerのkidで用途ごとに鍵を分けるという運用もあります。 他には、Payloadにuseusageのような値を含んで用途を表現するというやり方があります。

ritou.hatenablog.com

JWTBootCamp2020 056

これらのどれを使うかについては、利用するサービスの設計やライブラリの状況から柔軟に判断すべきです。

JWTBootCamp2020 057

例えば機能単位で完全に鍵をわけられるような場合はkidに用途を含むことで排他的に検証が可能でしょう。

JWTBootCamp2020 058

鍵の管理には手を出せないがHeaderを自由に指定できる場合はtypの値を利用できるでしょう。ちなみに、ライブラリでこの検証をするものもあります。

JWTBootCamp2020 059

Payloadしか指定できない場合はPayloadに独自の値を指定する必要があるでしょう。 ライブラリでもサポートされていない部分なので、実装漏れがないように気をつける必要があります。

最後に紹介しますが、JWTの署名検証では Header -> Signature -> Payload という順番に扱っていくため、署名検証の処理回数が多い場合はHeaderの参照だけで判断できる方がPayloadの値を参照するよりも負荷がちょっとだけ抑えられるのではと思います。

JWT生成/検証デモ

JWTBootCamp2020 060

最後にJWTの簡単な生成と検証のデモをやってみます。 (予想通り時間もなかったのでざっと流しました。)

JWTBootCamp2020 061

目的としては、簡単なやつでもこれまで紹介した仕様について手を動かすことで理解が深まるかなと言うところです。 業務では、ライブラリを使いましょう。Elixirでは KittenBlue っていうのが便利ですよきっと。

JWTBootCamp2020 062

ここではJWT用のライブラリを使わず、これらの3つの機能を使ってやってみます。 Base64URL Encode/Decode は padding のオプションがあったりするので注意が必要です。

生成

JWTBootCamp2020 063

生成の流れを説明します。 一言で言うと、Header, Payload の値を生成した後、Signature の値を生成して連結します。

1つずつ見ていきましょう。最初はHeaderです。

JWTBootCamp2020 064

ここでJWTに含むHeaderパラメータは3つです。

  • typ ではハンズオンっぽい文字列を用意して、用途を判別できるようにします
  • alg 署名アルゴリズムにはHMAC SHA-256を使います
  • kid 鍵管理を意識しましょうと言ったはずだ

JWTBootCamp2020 065

まずは JSON Encode します。 それを Base64URL Encode すると Encoded Header が出来上がりです。

JWTBootCamp2020 066

次にPayloadです。 ここでは送りたいデータとしてこんな感じのを用意します。 せっかく紹介したんだからRFC7519で定義されている iss とか aud とか exp の値を使えばよかったなと後から思いましたがとりあえずこれでヨシ!

JWTBootCamp2020 067

Headerと同様に、JSON Encode と Base64URL Encode したら Encoded Payload の出来上がりです。 次はSignatureを生成します。

JWTBootCamp2020 068

Signature生成のもとになる文字列は、Encoded HeaderとEncoded Payloadを "." で連結させたものです。正規化不要!簡単ですね。

JWTBootCamp2020 069

今回は鍵として "THIS_IS_SAMPLE_KEY_FOR_JWT_HANDSON" とかを用意して、それで HMAC-SHA256 した値をBase64URL EncodeするとEncoded Signatureの出来上がりです。

JWTBootCamp2020 070

最後に連結させます。 Elixirだとこんな感じです。

iex(1)> encoded_header = %{"alg"=>"HS256", "kid"=>"handson01", "typ"=>"handson+JWT"} |> Jason.encode!() |> Base.url_encode64(padding: false)
"eyJhbGciOiJIUzI1NiIsImtpZCI6ImhhbmRzb24wMSIsInR5cCI6ImhhbmRzb24rSldUIn0"

iex(2)> encoded_payload = %{"Foo"=>"Bar", "Hoge"=>"Fuga"} |> Jason.encode!() |> Base.url_encode64(padding: false)
"eyJGb28iOiJCYXIiLCJIb2dlIjoiRnVnYSJ9"

iex(3)> signature_base_string = encoded_header <> "." <> encoded_payload
"eyJhbGciOiJIUzI1NiIsImtpZCI6ImhhbmRzb24wMSIsInR5cCI6ImhhbmRzb24rSldUIn0.eyJGb28iOiJCYXIiLCJIb2dlIjoiRnVnYSJ9"

iex(4)> encoded_signature = :crypto.hmac(:sha256, "THIS_IS_SAMPLE_KEY_FOR_JWT_HANDSON", signature_base_string) |> Base.url_encode64(padding: false)
"Tp0zcg2nEA1r94EijoymQTTVMwH6iaLoOpxEZf3KcVM"

iex(5)> jwt = encoded_header <> "." <> encoded_signature <> "." <> encoded_signature
"eyJhbGciOiJIUzI1NiIsImtpZCI6ImhhbmRzb24wMSIsInR5cCI6ImhhbmRzb24rSldUIn0.Tp0zcg2nEA1r94EijoymQTTVMwH6iaLoOpxEZf3KcVM.Tp0zcg2nEA1r94EijoymQTTVMwH6iaLoOpxEZf3KcVM"

公開鍵暗号系のalgを利用する場合は、Headerのパラメータで指定してSignature計算のところで適切な署名生成関数を利用します。

生成については割と簡単なので、Qiitaなどで生成を自前でやるのを見かけますが、複数箇所でハードに使っていくとなると共通処理がまとめられ、結果ライブラリを使うのと変わらなくなると思います。

検証

生成ができたので検証もやってみましょう。

JWTBootCamp2020 071

検証の場合、順番が変わります。

Signatureの検証のために先にHeaderの値を参照する必要があり、Payloadの内容を参照するのはSignature検証の後となります。

JWTBootCamp2020 072

生成と逆の手順で、Base64URL DecodeしてJSON Decodeして Header パラメータを取得します。ここで含まれる値であれば、

  1. typ パラメータの値で用途の検証
  2. kid の値が有効なものかどうか
  3. alg の値が kid に紐づいた値と一致しているかどうか

と言う検証を行います。

先ほど述べたように、生成時に alg の値を含まず、省略して3もスキップしても良いでしょう。

JWTBootCamp2020 073

次にSignatureの検証をします。 Base StringはEncoded Header と Encoded Payload を "." で連結したものです。

JWTBootCamp2020 074

kid に紐づく鍵で生成時と同様に値を計算し、Base64URL Encodeした値と Encoded Signature の値を比較します。

JWTBootCamp2020 075

Signatureの検証を終えたら、Payloadの値を参照します。 ここからはJWTを利用するサービスごとによしなにと言うところです。

RFC7519で定義されている iss, aud, exp などのクレームを含む場合はここで検証します。

検証の手順は以上です。

外部サービス

jwt.io というJWTの生成/検証を行う外部サービスがあり、JWTのデバッガがついています。

jwt.io

ここまでで生成したJWTを入れて鍵情報も入れてやると、署名検証を行い成功したことがわかります。

JWTBootCamp2020 076

公開鍵暗号を使うalg=RS256も紹介したかったですが時間がなさそうなのでやめました。 興味がある方は試してみてください。質問などありましたらいつでもどうぞ。

JWTのデバッガは、他にもmicrosoft製のjwt.msなどがあります。 jwt.ms: Welcome!

外部サービスなので、本番環境で単体でAPIアクセスに使えるようなJWTを入力したり、業務で扱う秘密鍵を指定しての署名検証などは避けましょう。

まとめ

  • JSON Web Tokenの概要を紹介しました
  • 様々なサービスで使われているJSON Web Signatureと関連する仕様を紹介しました
  • 実装で気をつけるべきポイントを紹介しました
  • ハンズオン的にJWTの生成/検証を行う方法を紹介しました

初学者向けにまとめたつもりでしたが、もっとユースケース毎にこんな情報をこんな感じで含むといいよねみたいな話や実装についてももうちょっと紹介できると良かったかなとちょっと反省しています。


と、こんな感じでした。 今後もユーザー認証とかリソースアクセスとか、おじさんの守備範囲でその3ぐらいまではやってみたいと思っています。

speakerdeck.com

ではまた!