機雷がなんだ! 全速前進!

SEというかプログラマというか、日々のエンジニア生活の中で体験したことなどを中心に書き残しています。

Microsoft Entra ID (旧 Azure AD) をIdPとしてOIDC認証する際の注意点(フロントエンド)

AWS CognitoでOIDC認証していたWebアプリケーション(SPA)のIdPをMicrosoft Entra ID (旧 Azure AD) に変更した時に遭遇した注意点について記載します。

本記事に記載した注意点を実際に動かして確認したい場合は、以下のサンプルアプリ(Reactで実装されたSPA)を使えば簡単に試すことができます。

github.com

変更前IdP:Amazon Cognito

Amazon CognitoをIdPとして以下のようなエンドポイントを指定していました。

フロントエンドの設定

以下のようなエンドポイントを指定しました。

VITE_OIDC_AUTHORITY=https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xxxxxxxxx
VITE_OIDC_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
VITE_OIDC_REDIRECT_URI=http://localhost:5173
VITE_OIDC_SCOPE=openid profile email

この設定で特に問題なくOIDC認証ができていました。

変更後IdP:Microsoft Entra ID (旧 Azure AD)

Microsoft Entra ID (旧 Azure AD)をIdPに変更したところいくつかの問題に遭遇しました。

遭遇1:アクセストークンがInvalid Signature (無効な署名)になる

手順①:Microsoft Entra ID でアプリを新規登録

Azure ポータルのMicrosoft Entra ID で以下のようにアプリを登録しました。

概要

管理 -> Authentication (Preview):リダイレクトURIの構成

管理 -> API のアクセス許可

手順②:フロントエンドの設定

以下のようなエンドポイントを指定しました。

VITE_OIDC_AUTHORITY=https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0
VITE_OIDC_CLIENT_ID=4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b
VITE_OIDC_REDIRECT_URI=http://localhost:5173
VITE_OIDC_SCOPE=openid profile email

手順③:実行結果

取得したアクセストークンの署名検証結果がInvalid Signature(無効な署名)になってしまいました。 アクセストークンを確認してみると audience(aud)がMicrosoft Graphになっていました。(以下❌のところ)

{
  "aud": "00000003-0000-0000-c000-000000000000", ← ❌ Microsoft Graph になっている
  "iss": "https://sts.windows.net/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/",
  "iat": 1771046123,
  "nbf": 1771046123,
  "exp": 1771051527,
  "acct": 0,
  "acr": "1",
  "acrs": [
    "p1"
  ],
  "aio": "xxxxx",
  "altsecid": "1:live.com:XXXXXXXXXXXXXXXX",
  "amr": [
    "pwd",
    "mfa"
  ],
  "app_displayname": "web-app-sample",
  "appid": "4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b",
  "appidacr": "0",
  "email": "xxxxxxxxxx@gmail.com",
  "family_name": "山田",
  "given_name": "一郎",
  "idp": "live.com",
  "idtyp": "user",
  "ipaddr": "xxx.xxx.xxx.xxx",
  "name": "山田 一郎",
  "oid": "0bf5xxxx-xxxx-xxxx-xxxx-xxxxxxd0f6a6",
  "platf": "3",
  "puid": "1003200378EAFE7B",
  "rh": "xxxxx",
  "scp": "email User.Read profile openid",
  "sid": "0020xxxx-xxxx-xxxx-xxxx-xxxxxx5f5cab",
  "signin_state": [
    "kmsi"
  ],
  "sub": "xxxxx",
  "tenant_region_scope": "JPR",
  "tid": "4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb",
  "unique_name": "live.com#xxxxxxxxxx@gmail.com",
  "uti": "xxxxx",
  "ver": "1.0",
  "wids": [
    "62e9xxxx-xxxx-xxxx-xxxx-xxxxxx145e10",
    "b79fxxxx-xxxx-xxxx-xxxx-xxxxxxe85509"
  ],
  "xms_acd": xxxxx,
  "xms_act_fct": "X X",
  "xms_ftd": "xxxxx"
  "xms_idrel": "X XX",
  "xms_st": {
    "sub": "xxxxx"
  },
  "xms_sub_fct": "X XX",
  "xms_tcdt": xxxxx,
  "xms_tnt_fct": "X XX"
}

これは以下の記事と同じことが発生しており Microsoft Entra IDとフロントエンドの両方で設定の見直しが必要です。

Microsoft ID プラットフォームのアクセストークンが”Invalid Signature”になる件 その1:Express + Passport編 | 株式会社アイオス


遭遇2:アクセストークンのバージョンが想定外(v1.0 トークン)

手順①:Microsoft Entra ID でアプリに設定追加

Azure ポータルのMicrosoft Entra ID で以下のようにアプリ設定を追加しました。

管理 -> API の公開

【↓】スコープ名は「web-app-backend」としています。

手順②:フロントエンドの設定

以下のようなエンドポイントを指定しました。

VITE_OIDC_AUTHORITY=https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0
VITE_OIDC_CLIENT_ID=4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b
VITE_OIDC_REDIRECT_URI=http://localhost:5173
VITE_OIDC_SCOPE=openid profile email api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b/.default

手順③:実行結果

今回は得したアクセストークンの署名検証結果がSignature Verified(署名確認済み)になってしまいました。 アクセストークンを確認してみると audience(aud)やscope(scp)が登録したアプリのものに変更されていました。(以下✅のところ) しかしフロントエンドの設定でエンドポイントは2.0を指定したのに、iss(issuer)やアクセストークンのver(version)が「1.0」となっています。(上記❌のところ)

{
  "aud": "api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b", ← ✅ 登録したアプリになっている
  "iss": "https://sts.windows.net/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/", ← ❌ v1.0 のエンドポイント
  "iat": 1771043342,
  "nbf": 1771043342,
  "exp": 1771048647,
  "acr": "1",
  "aio": "xxxxx",
  "amr": [
    "pwd",
    "mfa"
  ],
  "appid": "4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b",
  "appidacr": "0",
  "email": "xxxxxxxxxx@gmail.com",
  "family_name": "山田",
  "given_name": "一郎",
  "idp": "live.com",
  "ipaddr": "xxx.xxx.xxx.xxx",
  "name": "山田 一郎",
  "oid": "0bf5xxxx-xxxx-xxxx-xxxx-xxxxxxd0f6a6",
  "rh": "xxxxx",
  "scp": "web-app-backend", ← ✅ 登録したアプリになっている
  "sid": "0020xxxx-xxxx-xxxx-xxxx-xxxxxx3fb9da",
  "sub": "xxxxx",
  "tid": "4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb",
  "unique_name": "live.com#xxxxxxxxxx@gmail.com",
  "uti": "xxxxx",
  "ver": "1.0", ← ❌ v1.0 トークン
  "xms_ftd": "xxxxx"
}

最終版:アクセストークンのバージョンを是正(v2.0 トークンへ)

手順①:Microsoft Entra ID でアプリの設定変更

Azure ポータルのMicrosoft Entra ID で以下のようにアプリ設定を変更しました。

管理 -> マニフェスト

変更前: "accessTokenAcceptedVersion": null,  ← v1.0 トークンを発行(デフォルト値)
変更後: "accessTokenAcceptedVersion": 2,     ← v2.0 トークンを発行

手順②:フロントエンドの設定

以下のようなエンドポイントを指定しました。(※問題2と同様のため変更不要です)

VITE_OIDC_AUTHORITY=https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0
VITE_OIDC_CLIENT_ID=4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b
VITE_OIDC_REDIRECT_URI=http://localhost:5173
VITE_OIDC_SCOPE=openid profile email api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b/.default

手順③:実行結果

アクセストークンを確認してみるとちゃんとiss(issuer)やver(version)が「2.0」になっていました。(以下✅のところ)

{
  "aud": "4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b",
  "iss": "https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0", ← ✅ v2.0 のエンドポイント
  "iat": 1771043595,
  "nbf": 1771043595,
  "exp": 1771048913,
  "aio": "xxxxx",
  "azp": "4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b",
  "azpacr": "0",
  "idp": "live.com",
  "name": "山田 一郎",
  "oid": "0bf5xxxx-xxxx-xxxx-xxxx-xxxxxxd0f6a6",
  "preferred_username": "xxxxxxxxxx@gmail.com",
  "rh": "xxxxx",
  "scp": "web-app-backend",
  "sid": "0020xxxx-xxxx-xxxx-xxxx-xxxxxx5f5cab",
  "sub": "xxxxx",
  "tid": "4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb",
  "uti": "xxxxx",
  "ver": "2.0", ← ✅ v2.0 トークンになっている
  "xms_ftd": "xxxxx"
}

補足

上記の例ではフロントエンドの設定でscope(scp)を以下のように指定しましたが、末尾の .default というのがワイルドカード的な意味(全て)になっています。

VITE_OIDC_SCOPE=openid profile email api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b/.default

より厳密に制御する必要がある場合は、以下のように許可するスコープ名を個別に指定するようにします。

VITE_OIDC_SCOPE=openid profile email api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b/web-app-backend

ただし今回の例では、そもそもスコープが1つしか定義されていないため、いずれもアクセストークンのscope(scp)は同じ内容になります。

おわりに

当初は、各IdPともにOIDCという標準的な共通のルールに則って実装されているだろうからフロントエンドのエンドポイントの向き先を変更するだけでサクッと変更できるものと考えていましたが、実際にやってみると各IdPごとに違いがあり、クライアント側だけでなくIdP側も仕様を理解して正確に設定しないと期待した通り動作してくれませんでした。もちろん知っていれば非常に簡単な設定ではあるのですが、当初の認識の甘さも相まって期待した挙動になるまでに、何度も試行錯誤することになりました。このあたりの見通しの甘さは反省が必要ですね。