フューチャー技術ブログ

Entra IDを使うウェブサービスのバックエンドのテスト

Entra ID(旧名Azure AD)は企業での利用の割合が高く、社内システムを作る場合はこれを使って認証して欲しいという要件が積まれることがほとんどでしょう。とはいえ、現物を使ってテストを作るのは都合がよくないこともあったりします。例えば処理時間が延びる(大量のE2Eテストを走らせる場合)とか、たくさんのユーザーのバリエーションを作る場合にそれだけユーザーを登録しないといけないとか、多要素認証の認証をどうするかとか。そんな感じのウェブサービスの単体テストを簡略化する方法について検討して組み込んだので紹介します。

前提

Entra IDを組み込む方法については以前のエントリーで紹介しています。

後者は他の認証基盤でもだいたい同じですが、前者は、MSAL.jsのおかげでフロントエンドのみで認証が完了するということを紹介しました。多くの場合はサーバーにもコールバックがきたりとか、サーバーもログイン処理の一部に入っているのですが、そこが不要となっています。そのため、バックエンドとしては有効なJWTがリクエストについてきて、その署名の検証だけすればOKという状態となっています。

問題はその署名入りのJWTトークンをどうつくるか、という一点に絞られます。

ちなみに、JWTのTがトークンでJWTトークンと書くと「JSONウェブトークン・トークン」となってしまうのですが、毎回JWTをロングで書くのも面倒ですし、JWTだけだと不親切かと思いますJWTトークンにしています。江戸川や利根川を英語に翻訳するときに「エドガワーリバー」とか「トネガワリバー」とかになるのと同じようなものだと思っていただければ。

自前で署名とJWTトークンを作る

Entra IDの場合、 https://login.microsoftonline.com/{テナントID}/v2.0/.well-known/openid-configuration にアクセスし、その中の jwks_uriというキーを見ると、署名検証のためのJWKの公開鍵が手に入ります。おそらくはhttps://login.microsoftonline.com/{テナントID}/discovery/v2.0/keysというURLのはずですが。ですが、この公開鍵はEntra IDが内部で保持している秘密鍵を使った署名の検証にしか使えないため、テスト用のJWTトークンを作る場合はこの鍵は使えません。そのため、まずはローカルの公開鍵と暗号鍵のペアを作ります。以下のサイトを使うのが簡単でしょう。

RSAタブを開き以下のように入れるとEntra IDっぽくなります。KeyIDは任意です。

  • Key Size(鍵のビット数): 2048
  • Key Use(鍵の用途): Signature
  • Algorithm(アルゴリズム): RS256
  • Key ID(鍵の識別子): “for test”
  • Show X.509(): No

生成されたら、一番左と一番右をそれぞれJWTトークン作成用に保存しておきます。真ん中はサーバーで利用するのに便利な情報なのでこれも保存しておきます。

image.png

JWTトークンを作成

次にJWTを作ります。テナントIDとアプリケーションIDはテスト環境のEntraIDで生成したものと同じものを使う、あるいは独自に作るでもどちらでもよいですが決めておきます。UUIDの型式です。このサンプルではテナントIDを0000000-0000-0000-0000-000000000000、アプリケーションIDを1111111-1111-1111-1111-111111111111とします。

Entra ID相当のトークンを作りたいので、こんな感じのclaimを用意します。重要な項目は以下の通りです。

{
"aud": "1111111-1111-1111-1111-111111111111",
"iss": "https://login.microsoftonline.com/0000000-0000-0000-0000-000000000000/v2.0",
"iat": 1671015703,
"nbf": 1671015703,
"exp": 2071019603,
"idp": "https://sts.windows.net/3eca0868-d511-4342-8659-e88a2e3bf9fe/",
"name": "Test User(テストユーザー)",
"nonce": "ce3fd167-0e5a-43ae-bb8b-11d8d003d8c6",
"oid": "daf6a6c6-d549-4421-bf71-3b59fd74d531",
"preferred_username": "test.user@example.co.jp",
"rh": "0.AWoA733wxZg0tkymktoZGQzOJ_1Ryon2gERMsUs1n-XnFcpqAFQ.",
"sub": "oXxd31705vfpTnrSPcdVCdAoalq7ZgQ_gx7Msq7OBzY",
"tid": "0000000-0000-0000-0000-000000000000",
"uti": "Fw7ZoF0z1UCipEF8hfwZAA",
"ver": "2.0"
}

subはユーザーを表すユニークなキーとされていますが、Entra ID上のこのsubと、実際のシステム上のユーザーの対応付けは別途決めておく必要があります。Entra IDとHRのシステムの同期をきちんととっておくのであればこのsubを使ってユーザーを決められますが、そうでない場合はマニフェストを変更してユーザーコード相当の情報を登録したり、preferred_usernameを使うといったことが必要になります。そのあたりはサーバー側の方針に合わせて上記のclaimは修正してください。

次にjwt.ioを開きます。

https://jwt.io/

アルゴリズムをRS256に変更し、PAYLOADに上記のclaimを、その下の署名欄の上の公開鍵にはmkjwk.orgで作った公開鍵を、その下の秘密鍵にはmkjwk.orgで作った秘密鍵を貼り付けます。

これで左側にJWTが生成されます。

image.png

これはmkjws.orgで作成したjwks.orgの証明書を使って署名の確認が行えます。サーバー側でJWTトークンの検証を行うときは、Entra IDの秘密鍵ではなく、mkjwk.orgで生成した秘密鍵(真ん中のPublic and Private Keypair Setが同じ形式なので扱いやすい)を使って署名の検証が行えます。任意のユーザー情報を作ってトークンを量産できますし、実際のログインは不要なのでテストでも扱いやすいでしょう。このトークンを使えばcurlでもなんでも自由にリクエストが飛ばせるようになります。