JSON Web Token(JWT、「ジョット」と発音)は、ID情報を交換するためのコンパクトで移植性の高い手段です。JWT仕様は、OpenID Connectの重要な基盤として、OAuth 2.0エコシステムにシングルサインオン用トークンを提供してきました。JWTは、それ自体を認証情報として使用することもでき、従来のAPIキーよりもWebベースのAPIへのアクセスを適切に制御できます。
NGINX Plus R10以降では、JWTを直接検証できます。このブログ記事では、NGINX PlusをAPIゲートウェイとして使用し、APIエンドポイントのフロントエンドを提供するとともに、JWTを使用してクライアントアプリケーションを認証する方法について説明します。
JWTのネイティブサポートはNGINX Plusでのみ利用でき、NGINX Open Sourceでは利用できません。
編集者注 – このブログ記事は、NGINX Plus R25で導入された更新されましたを使用するよう、2021年12月にauth_jwt_requireディレクティブを使用するよう更新されました。このディレクティブの詳細については、NGINX Plus R25の発表ブログにある「カスタムJWT検証ルール」をご覧ください。
NGINX Plus R15以降では、OpenID Connect 1.0の「認可コードフロー」も制御できるため、主要なアイデンティティプロバイダーのほとんどと統合できます。詳細については、「NGINX Plus R15の発表<.htmlspan>」を参照してください。
JWTの構造
JWTは、ヘッダー、ペイロード、署名の3つの部分で構成されます。送信時の形式は次のとおりです。読みやすいように改行を加え(実際のJWTは1つの文字列です)、3つの部分を区別できるよう色分けしています。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICAgInN1YiI6ICJsYzEiLAogICAgImVtYWlsIjogImxpYW0uY3JpbGx5QG5naW54LmNvbSIsCn0=.VGYHWPterIaLjRi0LywgN3jnDUQbSsFptUw99g2slfcご覧のとおり、ピリオド( . )でヘッダー、ペイロード、署名が区切られています。ヘッダーとペイロードは、Base64エンコードされたJSONオブジェクトです。署名は、algヘッダーで指定されたアルゴリズムを使用して暗号化されています。サンプルのJWTをデコードすると、これを確認できます。
エンコード済み デコード済みヘッダー eyJhbGciOiJIUzI1NiIsInR5cCI6Ik pXVCJ9 {ペイロード
"alg": "HS256",
"typ": "JWT"
}ewogICAgInN1YiI6ICJsYzEiLAogICAgImVtYWlsIjogImxpYW0uY3JpbGx5QG5naW54LmNvbSIsCn0= {
"sub": "lc1",
"email": "liam.crilly@nginx.com",
}
The JWT標準では、複数の署名アルゴリズムが定義されています。この例の値「HS256」はHMAC SHA‑256を指し、このブログ記事に掲載するすべてのサンプルJWTで使用しています。NGINX Plusは、HSxxx、RSxxx、およびESxxxの署名アルゴリズムをサポートしています。これらは標準で定義されています。JWTは暗号技術を用いて署名できるため、認証情報としての使用に適しています。
APIキーとしてのJWT
APIクライアント(APIリソースを要求するリモートソフトウェアクライアント)の一般的な認証方法は、通常「APIキー」と呼ばれる共有シークレットを使用することです。従来のAPIキーは、基本的に長く複雑なパスワードであり、クライアントはリクエストのたびに追加のHTTPヘッダーとして送信します。指定されたAPIキーが有効なキーのリストに含まれている場合、APIエンドポイントは要求されたリソースへのアクセスを許可します。通常、APIエンドポイント自体はAPIキーを検証しません。代わりに、APIゲートウェイが認証プロセスを処理し、各リクエストを適切なエンドポイントにルーティングします。これにより、計算処理のオフロードに加えて、高可用性や複数のAPIエンドポイントへの負荷分散など、リバースプロキシによるメリットも得られます。

APIクライアントごとに異なるアクセス制御やポリシーを適用するのが一般的です。従来のAPIキーでは、APIキーを一連の属性と照合するための検索が必要です。リクエストのたびにこの検索を実行すると、システム全体のレイテンシーに相応の影響が生じます。JWTにはこれらの属性が埋め込まれているため、別途検索する必要はありません。
JWTをAPIキーとして使用することで、認証技術のベストプラクティスと、ID属性を交換するための標準ベースのスキーマを組み合わせた、従来のAPIキーに代わる高性能な手段を実現できます。

NGINX Plusを認証APIゲートウェイとして構成する
JWTを検証するためのNGINX Plusの設定は非常にシンプルです。
upstream api_server {
server 10.0.0.1;
server 10.0.0.2;
}
server {
listen 80;
location /products/ {
auth_jwt "Products API";
auth_jwt_key_file conf/api_secret.jwk;
proxy_pass http://api_server;
}
}まず、APIエンドポイントをホストするサーバーのアドレスをupstreamブロックで指定します。locationブロックでは、/products/で始まるURLへのすべてのリクエストに認証が必要であることを指定します。auth_jwtディレクティブでは、認証に失敗した場合に401ステータスコードとともに返される認証レルムを定義します。
The auth_jwt_key_fileディレクティブは、JWTの署名要素を検証する方法をNGINX Plusに指示します。この例では、HMAC SHA‑256アルゴリズムを使用してJWTに署名するため、署名に使用する対称鍵を格納するJSON Web Keyをconf/api_secret.jwkに作成する必要があります。このファイルは、JSON Web Key仕様に記載された形式に従う必要があります。この例では、次のようになります。
{"keys":
[{
"k":"ZmFudGFzdGljand0",
"kty":"oct",
"kid":"0001"
}]
}対称鍵はkフィールドで定義されており、ここでは平文の文字列「Base64URL‑encoded」ではなく、「fantasticjwt」をBase64URLエンコードした値を使用しています。このエンコード値は、次のコマンドを実行して取得しました。
$ echo -n fantasticjwt | base64 | tr '+/' '-_' | tr -d '='The ktyフィールドでは、キータイプを対称キー(オクテットシーケンス)として定義します。最後に、kid(Key ID)フィールドでは、このJSON Web Keyのシリアル番号(ここでは0001)を定義します。これにより、auth_jwt_key_fileディレクティブで指定した同じファイル内で複数のキーをサポートし、それらのキーと、そのキーで署名されたJWTのライフサイクルを管理できます。
これで、APIクライアントにJWTを発行する準備が整いました。
APIクライアントへのJWTの発行
サンプルのAPIクライアントとして「見積システム」アプリケーションを使用し、このAPIクライアント用のJWTを作成します。まず、JWTヘッダーを定義します。
{
"typ":"JWT",
"alg":"HS256",
"kid":"0001"
}The typフィールドではタイプをJSON Web Tokenとして定義し、algフィールドではJWTがHMAC SHA256アルゴリズムで署名されていることを指定します。また、kidフィールドではJWTがそのシリアル番号のJSON Web Keyで署名されていることを指定します。
次に、JWTペイロードを定義します。
{
"name":"Quotation System",
"sub":"quotes",
"iss":"My API Gateway"
}The sub(subject)フィールドは、nameフィールドの完全な値を一意に識別するための識別子です。issフィールドはJWTの発行者を示します。APIゲートウェイがサードパーティーの発行者や一元化されたID管理システムからのJWTも受け入れる場合に役立ちます。
JWTの作成に必要なものがすべて揃ったので、以下の手順に従って正しくエンコードし、署名します。コマンドとエンコードされた値は、読みやすくするために複数行で表示していますが、実際にはそれぞれ1行で入力または表示されます。
- ヘッダーとペイロードをそれぞれ1行にまとめ、Base64URLエンコードします。
$ echo -n '{"typ":"JWT","alg":"HS256","kid":"0001"}' | base64 | tr '+/' '-_' | tr -d '='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEifQ
$ echo -n '{"name":"Quotation System","sub":"quotes","iss":"My API Gateway"}' | base64 | tr '+/' '-_' | tr -d '='
eyJuYW1lIjoiUXVvdGF0aW9uIFN5c3RlbSIsInN1YiI6InF1b3RlcyIsImlzcyI6Ik
15IEFQSSBHYXRld2F5In0- エンコードしたヘッダーとペイロードをピリオド(.)で連結し、その結果を
HEADER_PAYLOAD変数に代入します。
$ HEADER_PAYLOAD=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEifQ.eyJuYW1lIjoiUXVvdGF0aW9uIFN5c3RlbSIsInN1YiI6InF1b3RlcyIsIm lzcyI6Ik15IEFQSSBHYXRld2F5In0- ヘッダーとペイロードを対称鍵で署名し、その署名をBase64URLエンコードします。
$ echo -n $HEADER_PAYLOAD | openssl dgst -binary -sha256 -hmac fantasticjwt | base64 | tr '+/' '-_' | tr -d '='ggVOHYnVFB8GVPE-VOIo3jD71gTkLffAY0hQOGXPL2I- エンコードした署名をヘッダーとペイロードに追加します。
$ echo $HEADER_PAYLOAD.ggVOHYnVFB8GVPE-VOIo3jD71gTkLffAY0hQOGXPL2I > quotes.jwt- APIゲートウェイに認証済みリクエストを送信してテストします(この例では、ゲートウェイはlocalhostで実行されています)。
$ curl -H "Authorization: Bearer `cat quotes.jwt`" http://localhost/products/widget1ステップ5のcurlコマンドは、JWTをBearer Tokenの形式でNGINX Plusに送信します。これは、NGINX Plusがデフォルトで想定する形式です。NGINX Plusは、Cookieまたはクエリ文字列パラメーターからJWTを取得することもできます。このように設定するには、token=パラメーターをauth_jwtディレクティブに含めます。たとえば、次の設定により、NGINX Plusは以下のcurlコマンドで送信されたJWTを検証できます。
$ curl http://localhost/products/widget1?apijwt=`cat quotes.jwt`server {
listen 80;
location /products/ {
auth_jwt "Products API" token=$arg_apijwt;
auth_jwt_key_file conf/api_secret.jwk;
proxy_pass http://api_server;
}
}上記のとおりNGINX Plusを設定し、JWTを生成して検証したら、そのJWTをAPIクライアントの開発者に送信し、各APIリクエストでJWTを送信するための仕組みについて合意する準備が整います。
ログ記録とレート制限へのJWTクレームの活用
認証情報としてJWTを使用する主な利点の1つは、JWTとそのペイロードに関連するエンティティ(発行者、発行先のユーザー、想定される受信者など)を表す「クレーム」を伝達できることです。JWTを検証すると、NGINX Plusはヘッダーとペイロード内のすべてのフィールドに変数としてアクセスできます。目的のフィールド名の先頭に「$jwt_header_」または「$jwt_claim_」を付けることでアクセスできます(たとえば、「sub」クレームの場合は「$jwt_claim_sub」)。そのため、API自体にJWT処理を実装しなくても、JWTに含まれる情報をAPIエンドポイントへ容易にプロキシできます。
この構成例では、高度な機能の一部を示します。
log_format jwt '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'$jwt_header_alg $jwt_claim_sub';
limit_req_zone $jwt_claim_sub zone=10rps_per_client:1m rate=10r/s;
server {
listen 80;
location /products/ {
auth_jwt "Products API";
auth_jwt_key_file conf/api_secret.jwk;
limit_req zone=10rps_per_client;
proxy_pass http://api_server;
proxy_set_header API-Client $jwt_claim_sub;
access_log /var/log/nginx/access_jwt.log jwt;
}
}The log_formatディレクティブでは、jwtという新しい形式を定義します。この形式は、共通ログ形式に$jwt_header_algと$jwt_claim_subの2つのフィールドを追加したものです。locationブロック内では、access_logディレクティブを使用して、検証済みJWTから取得した値をログに書き込みます。
この例では、クレームベースの変数も使用し、IPアドレス単位ではなくAPIクライアント単位でAPIのレート制限を行います。これは、複数のAPIクライアントが1つのポータルに組み込まれており、IPアドレスでは区別できない場合に特に有用です。limit_req_zoneディレクティブは、JWTのsubクレームをレート制限の計算キーとして使用します。算出されたレート制限は、locationブロックにlimit_reqディレクティブを含めることで適用されます。
最後に、リクエストがAPIエンドポイントにプロキシされる際、JWTのsubjectを新しいHTTPヘッダーとして渡します。proxy_set_headerディレクティブは、APIエンドポイントで容易に利用できる「API‑Client」というHTTPヘッダーを追加します。そのため、APIエンドポイントにJWT処理ロジックを実装する必要はありません。APIエンドポイントの数が増えるほど、この利点は大きくなります。
JWTの失効
APIクライアントのJWTを失効または再発行する必要が生じる場合があります。シンプルな「map」ブロックと「auth_jwt_require」ディレクティブを組み合わせることで、JWTを無効としてマークし、その有効期限(「exp」クレームで示される)に達するまでAPIクライアントからのアクセスを拒否できます。有効期限に達した時点で、そのJWTの「map」エントリを安全に削除できます。
この例では、トークン内の$jwt_status変数を、0または1に設定します。設定する値は、トークン内のsubクレームの値($jwt_claim_sub変数に格納された値)に基づきます。次に、auth_jwt_requireディレクティブをlocationブロックで使用して、トークンを追加で検証(または拒否)します。トークンが有効と見なされるには、$jwt_status変数が空ではなく、かつ0(ゼロ)でない必要があります。
map $jwt_claim_sub $jwt_status { "quotes" 0; "test" 0; default 1;
}
server {
listen 80;
location /products/ {
auth_jwt "Products API";
auth_jwt_key_file conf/api_secret.jwk;
auth_jwt_require $jwt_status;
proxy_pass http://api_server;
}
}まとめ
JSON Web Tokenは、APIへの認証済みアクセスの提供に適しています。APIクライアント開発者にとっては、従来のAPIキーと同様に簡単に扱えます。また、通常であればデータベースの参照が必要となるID情報をAPIゲートウェイに提供します。NGINX Plusは、JWT認証に加え、JWT自体に含まれる情報に基づく高度な構成ソリューションをサポートします。ほかのAPIゲートウェイ機能と組み合わせることで、NGINX Plusは、迅速性、信頼性、拡張性、セキュリティを備えたAPIベースのサービス提供を可能にします。
NGINX PlusでJWTを実際にお試しいただくには、今すぐ30日間の無料トライアルを開始するか、ユースケースについてご相談ください。
著者について

関連するブログ記事
チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。
AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。
JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。
F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。
NGINX PlusによるAWS Auto Scalingグループの負荷分散
新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります