🎄

OpenID Foundation の SSF、CAEP、RISC の概要と Microsoft Entra ID での実装について

2023/12/06に公開

本記事について

本記事は Digital Identity 技術勉強会 #iddance Advent Calendar 2023 6 日目の記事です。
https://qiita.com/advent-calendar/2023/iddance

本記事では、現在 Open ID Fundation で標準化が進められている SSF (Shared Signals Framework)、 CAEP (Continuous Access Evaluation Protocol)、RISC (Risk Incident Sharing and Coordination) の概要について説明し、 Microsoft Entra ID (旧 Azure AD) の継続的アクセス評価 (CAEP) に対応したクライアント アプリを作成する方法、および検証した内容について説明します。
記載している内容に誤りがございましたら修正いたしますため、 Twitter やコメントでご指摘いただけますと幸いです。

  • ID フェデレーションのセキュリティ的課題
  • SSF、 CAEP、 RISC について
  • Microsoft Entra ID の継続的アクセス評価について
  • 継続的アクセス評価に対応したクライアント アプリを作成する方法
  • 検証方法および検証結果
  • まとめ

ID フェデレーションのセキュリティ的課題

従来、各サービスに対してそれぞれ独立したアカウントを作成する必要がありましたが、OpenID Connect や SAML のような ID フェデレーション技術の発展により、ユーザーは単一のアカウントで複数のサービスへのアクセスが可能になるシングルサインオン(SSO)の恩恵を受けるようになりました。
これらの技術は、ユーザーの利便性を高める一方で、以下のようなセキュリティ懸念が存在します。

ID フェデレーションの技術では、 1 つの IdP に登録されたアカウント情報を用いて、複数のサービスを利用します。
もし、 1 つしかないメール アカウント情報が攻撃者によって乗っ取られた場合、攻撃者によって複数のサービスのアカウントを乗っ取られる可能性があります。

また、最初の認証時にはユーザーの妥当性を評価することができるが、セッションやアクセス トークンが攻撃者によって奪われた場合には、それらが有効期限内である間は、保護されている情報にアクセスされる可能性があるが、それを検知することは困難であるといった課題があります。
これらの課題を対処するために標準化が進められているのが、 SSF、 CAEP、 RISC です。

SSF、CAEP、 RISC について

SSF とは Open ID Fundation の Shared Signal and Events Working Group によって、上記のような課題を対処するために標準化が進められている仕様です。

対処案について端的に説明すると、 IdP と RP 間で Pub-Sub し、攻撃者に乗っ取られた可能性がある情報を IdP、 RP 間でリアルタイムで共有し検知しようというアプローチです。
CAEP と RISC は、 SSF で IdP と RP 間で共有するイベント (アカウントの失効など) 形式について定められています。
CAEP では、主にセッションに関するイベント、 RISC では、主にアカウントに関するイベントについて定められています。

これらのイベント形式は JSON Web Token(JWT)の一種である RFC 8417 Security Event Token (SET) 形式で定めらています。

セッションが失効されたときの形式の一例

{
    "iss": "https://idp.example.com/123456789/",
    "jti": "24c63fb56e5a2d77a6b512616ca9fa24",
    "iat": 1615305159,
    "aud": "https://sp.example.com/caep",
    "events": {
        "https://schemas.openid.net/secevent/caep/event-type/session-revoked": {
            "subject": {
                "format": "opaque",
                "id": "dMTlD|1600802906337.16|16008.16"
            },
            "event_timestamp": 1615304991643
        }
    }
}

また、イベントを送受信する方法については、以下を用いることが現在では記載されています。

Microsoft Entra ID の継続的アクセス評価について

Microsoft Entra ID では条件付きアクセスといった、トークン リクエスト時の scp クレームの値等からアクセスする RP を特定し、クライアントの IP アドレスや OS 、デバイスの情報を基にトークンの発行を制御し、 RP へのアクセスを制御する機能があります。

条件付きアクセスを利用することで、例えばメールの利用は社内ネットワークからのみ許可させるという制御が可能になります。

Microsoft Entra ID での条件付きアクセスの機能には、継続的アクセス評価と呼ばれる CAEP に基づいた機能があります。 CAEP に基づいていることは以下の引用部の通り MS の公開情報にも明記されています。

この会話のメカニズムは、Open ID Continuous Access Evaluation Profile (CAEP) に基づく業界標準である継続的アクセス評価 (CAE) です。
継続的アクセス評価

継続的アクセス評価は、 CAEP に基づいた機能なため、これまでトークン リクエスト時にしかクライアントの状態を検知することができなかった問題等を解決します。

具体的には、継続的アクセス評価により、アクセス トークンの有効期限内であっても、イベント (ユーザー セッションの失効、 IP アドレスの変更など) を検知した場合に RP 側でアクセスをブロックすることが可能になります。
なお、詳細につきましては、上記の公開情報をご参照ください。

継続的アクセス評価に対応したクライアント アプリを作成する方法

Microsoft Entra ID で CAEP に対応した API (RP) には Exchange Online、SharePoint Online、Teams、MS Graph がありますが、これらの API を使用するアプリを作成しようとしても、継続的アクセス評価の恩恵を受けることはできません。
というのも、 Ms Graph などの API では継続的アクセス評価に対応していないクライアント アプリに対しては、イベントが発生していたとしてもエラーを返さないような仕組みになっています。
具体的には、以下のようにアクセス トークン内の xms_cc クレームが "cp1" となっている場合に、クライアント アプリ側が継続的アクセス評価に対応していると判断し、エラーを返します。

これは、アクセス トークンの有効期限内に継続的アクセス評価によってブロックされた際に、アプリ側の実装 (アクセス トークンの有効期限が切れた場合にのみ再取得を試みるような実装がされている場合など)によっては、 API 呼び出しを再試行するループが発生する可能性があるため MS ではこのような対応策をとっています。

{
  "typ": "JWT",
  "nonce": "U2VXyUVs1elxXMWmRVJMM9AiE1Rl2i9pcWUGohpfvXs",
  "alg": "RS256",
  "x5t": "T1St-dLTvyWRgxB_676u8krXS-I",
  "kid": "T1St-dLTvyWRgxB_676u8krXS-I"
}.
{
  "aud": "00000003-0000-0000-c000-000000000000",
  "iss": "https://sts.windows.net/<tenant id>/",
  "iat": 1701611927,
  "nbf": 1701611927,
  "exp": 1701698627,
  "acct": 0,
  "acr": "1",
  "aio": "ATQAy/8VAAAAbKlAoXLeE1EX+0nNC5oCJn7mUkLlVljqG7C0fqCQbNdPfHGjrdMPA57U8/I0x6G3",
  "amr": [
    "pwd"
  ],
  "app_displayname": "Demo App",
  "appid": "d79f5709-****-****-****-************",
  "idtyp": "user",
  "name": "test",
  "oid": "e04a6336-****-****-****-************",
  "rh": "0.AXwAjiXMcpIh90uC5qM8b8bQXAMAAAAAAAAAwAAAAAAAAAC7ALw.",
  "scp": "User.Read profile openid email",
  "sub": "2l6****************************-eemCqo",
  "unique_name": "test@example.com",
  "upn": "test@example.com",
  "uti": "s70xI0k0B0Gpk0TwESNIAA",
  "ver": "1.0",
  "wids": [
    "b0f54661-2d74-4c50-afa3-1ec803f12efe",
  ],
+  "xms_cc": [
+    "cp1"
+  ],
  "xms_ssm": "1",
  "xms_st": {
    "sub": "hYdTZI31DOxJrR44STgr4xX9lcSROlMXiK8zW2Xf7nc"
  }
}.
[Signature]

この "xms_cc :["cp1"]" が含まれるアクセス トークンを取得するには、アプリ側で以下 2 つを実施する必要があります。

  • アプリ側から継続的アクセス評価に対応したアプリであることを Microsoft Entra ID に示す処理を追加する
  • 継続的アクセス評価によってアクセス トークンの有効期限内にエラーになった際に再認証を要求する処理を追加する

なお、 Microsoft が提供している MSAL ライブラリを使用すれば、簡単に継続的アクセス評価に対応したアプリを作成することができますが、 MSAL を使用していないアプリ側で継続的アクセス評価に対応することもあるかと思うため、 MSAL を使用していないアプリが継続的アクセス評価の恩恵を受けるために必要な処理について説明します。

アプリ側から継続的アクセス評価に対応したアプリであることを Microsoft Entra ID に示す方法

Microsoft Entra ID が継続的アクセス評価などの機能にアプリ側が対応していかは、認可コードおよびトークン リクエスト時の claims パラメーターの値を通じて行われます。
具体的には、"cliams" パラメータに "{"access_token":{"xms_cc":{"values":["cp1"]}}}" という値を指定します。

認可コード リクエスト時の例です。

GET https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/authorize
?client_id=2810aca2-****-****-****-************
&redirect_uri=https://hoge.com
&response_type=code
&scope=openid%20profile%20offline_access%20user.read
&response_mode=form_post
+ &claims=%7B%22access_token%22%3A%7B%22xms_cc%22%3A%7B%22values%22%3A%5B%22cp1%22%5D%7D%7D%7D

CAEP によってエラーになった際に再認証を要求する処理を追加する

エラー処理の主な流れは以下の通りです。

  1. エラー内の claims の値を抜き取る
  2. claims の値を Base64 デコードする
  3. デコードした claims の値に "xms_cc":{"values":["cp1"]} を加える
  4. 再認証を要求する

1. エラー内の claims の値を抜き取る

継続的アクセス評価により、エラーが発生した場合には、以下のようなレスポンスが API から返されます。
具体的には、 401 エラーで www-autheticate ヘッダーの claims を通して、アプリ側に新しいアクセストークンが必要な旨を伝えます。

HTTP 401; Unauthorized

www-authenticate =Bearer realm="", 
authorization_uri="https://login.microsoftonline.com/common/oauth2/authorize",
error="insufficient_claims",
claims="eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTY5OTc5MzAzOCJ9fX0="

2. claims の値を Base64 デコードする

www-authenticate の claims の値を base64 デコードします。
claims の値を base64 デコードした値の例は以下の通りです。

{
    "access_token": {
        "nbf": {
            "essential": true,
            "value": "1699793038"
        }
    }
}

3. claims の値に "xms_cc":{"values":["cp1"]}" を加える

calims の値に "xms_cc":{"values":["cp1"]} を加えます。

{
    "access_token": {
        "nbf": {
            "essential": true,
            "value": "1699793038"
        },
+        "xms_cc": {
+            "values": [
+                "CP1"
+            ]
+        }
    }
}

4. 再認証を要求する

Authorize エンドポイントに上記 claims の値を含め認可コードを要求します。

GET https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/authorize
?client_id=2810aca2-a927-4d26-8bca-5b32c1ef5ea9
&redirect_uri=https%3A%2F%contoso.com%3A44321%2Fsignin-oidc
&response_type=code
&scope=openid%20profile%20offline_access%20user.read
&response_mode=form_post
+ &claims=%7B%22access_token%22%3A%20%7B%22nbf%22%3A%20%7B%22essential%22%3A%20true%2C%20%22value%22%3A%20%221699793038%22%7D%2C%20%22xms_cc%22%3A%20%7B%22values%22%3A%20%5B%22CP1%22%5D%7D%7D%7D

アプリ側で継続的アクセス評価に対応する方法は以上となります。
なお、余談ではありますが、アプリよって送信されたアクセス トークンのクレームが不十分である際に API から送信される応答を claims challenge と呼んでいます。
(www-authenticate ヘッダーの claims に指示を送ること)
また、 claims challenge を受け取り、満たしていない要件を追加するために、 IdP にリダイレクトし、追加の要件を満たすクレームを含む新しいトークンを要求することを claims request と呼ばれています。
アプリ側の claims challenge を受け取り、 claims request が行えることを client capabilities と呼ばれています。

Claims challenges, claims requests, and client capabilities

検証方法および検証結果

実際に、 Microsoft Graph API を使用するアプリを用いて、ユーザー セッションを無効化した後に、有効期限内のアクセス トークンを用いた際にブロックされることを検証します。

今回の検証には、筆者が Microsoft Entra ID を検証するのに作成している CLI ツールを用いて検証を行います。
(今後 以下 CLI を用いて継続的アクセス評価を検証する手順も以下リポジトリに追加する予定です)
https://github.com/iamkdada/Azure-AD-OAuth-SAML-Python-Demo-CLI-APP/tree/main

  1. 認可コード フローで cliams" パラメータに "{"access_token":{"xms_cc":{"values":["cp1"]}}}" を追加し、アクセス トークンを取得します

    dada auth-code token-request --cae
    


    コマンド実施結果

    デコードしたトークン (一部マスクしています)
    {
      "typ": "JWT",
      "nonce": "FRfhdciM_jnV9Xv_FvdGSUOf7paFblo7PI6bfcoqr64",
      "alg": "RS256",
      "x5t": "T1St-dLTvyWRgxB_676u8krXS-I",
      "kid": "T1St-dLTvyWRgxB_676u8krXS-I"
    }.{
      "aud": "00000003-0000-0000-c000-000000000000",
      "iss": "https://sts.windows.net/72cc258e-****-****-****-************/",
      "iat": 1701617165,
      "nbf": 1701617165,
      "exp": 1701703865,
      "acct": 0,
      "acr": "1",
      "aio": "ATQAy/8VAAAA08RWwAAI5dIaJ1c97HKg2WWRZIjcE0cziQjjS9b2l1Nn+Ar1v8hJfI5/7AK3bmLH",
      "amr": [
        "pwd"
      ],
      "app_displayname": "Demo App",
      "appid": "d79f5709-****-****-****-************",
      "appidacr": "0",
      "family_name": "test",
      "given_name": "hoge",
      "idtyp": "user",
      "ipaddr": "***.***.***.***",
      "name": "test",
      "oid": "08a9eb0b-****-****-****-************",
      "platf": "3",
      "puid": "1003200321CAD0A9",
      "rh": "0.AXwAjiXMcpIh90uC5qM8b8bQXAMAAAAAAAAAwAAAAAAAAAC7AOA.",
      "scp": "User.Read profile openid email",
      "sub": "OmsyV6kCFEH95VhpoFljIjBU7zRcDHXjU43ztB4kdLg",
      "tenant_region_scope": "NA",
      "tid": "72cc258e-****-****-****-************",
      "unique_name": "test@iamkdada.onmicrosoft.com",
      "upn": "test@iamkdada.onmicrosoft.com",
      "uti": "4KLkz-3fyEyf5FGpwecUAA",
      "ver": "1.0",
      "wids": [
        "b79fbf4d-****-****-****-************"
      ],
      "xms_cc": [
        "cp1"
      ],
      "xms_ssm": "1",
      "xms_st": {
        "sub": "sUBVrPYSLDEWp9Mkdw5eKxoTvXN8Vmda_WyPZPklqKU"
      },
      "xms_tcdt": 1679416726
    }.[Signature]
    

    アクセス トークンの有効期限 (exp) はTue Dec 05 2023 00:31:05 GMT+0900 (日本標準時) で xms_cc クレームが含まれていることを確認できます。

  2. Graph API を呼び出し、アクセス トークンが有効であることを確認します

    dada auth-code graph-request -url "me"
    


    コマンド実施結果

  3. Azure ポータル上から該当ユーザーのセッションを取り消します

  4. 再度同様の Graph API を呼び出し、エラーになることを確認します

    dada auth-code graph-request -url "me"
    


    コマンド実施結果

    エラー メッセージ (Continuous access evaluation resulted in challenge with result: InteractionRequired and code: TokenIssuedBeforeRevocationTimestamp) を見ると、継続的アクセス評価によってエラーになっていることが分かると思います。
    また、時刻 (Mon Dec 4 00:41:35 JST 2023) を見ても、アクセス トークンの有効期限内であることが分かると思います。

  5. 再認証を行います

    dada auth-code token-request --cae
    

    セッションの取り消しを行ったため、パスワードの入力が求められます。

    パスワードを入力し、サインインを完了します。

    コマンド実施結果

    デコードしたアクセス トークン (一部マスクしています)
    {
      "typ": "JWT",
      "nonce": "0kun46aEqIIc5HMXDFQlWoo9hpw2JII1hLDJFq9gGbo",
      "alg": "RS256",
      "x5t": "T1St-dLTvyWRgxB_676u8krXS-I",
      "kid": "T1St-dLTvyWRgxB_676u8krXS-I"
    }.{
      "aud": "00000003-0000-0000-c000-000000000000",
      "iss": "https://sts.windows.net/72cc258e-****-****-****-************/",
      "iat": 1701618513,
      "nbf": 1701618513,
      "exp": 1701705213,
      "acct": 0,
      "acr": "1",
      "aio": "ATQAy/8VAAAAmgi4QLxt+98Q06lQ48UKLoPwiRa/A7dUUHhW/YKQLnoPeLWVKqB4NJEWuNQLah6T",
      "amr": [
        "pwd"
      ],
      "app_displayname": "Demo App",
      "appid": "d79f5709-****-****-****-************",
      "appidacr": "0",
      "family_name": "test",
      "given_name": "hoge",
      "idtyp": "user",
      "ipaddr": "***.***.***.***",
      "name": "test",
      "oid": "08a9eb0b-****-****-****-************",
      "platf": "3",
      "puid": "1003200321CAD0A9",
      "rh": "0.AXwAjiXMcpIh90uC5qM8b8bQXAMAAAAAAAAAwAAAAAAAAAC7AOA.",
      "scp": "User.Read profile openid email",
      "signin_state": [
        "kmsi"
      ],
      "sub": "OmsyV6kCFEH95VhpoFljIjBU7zRcDHXjU43ztB4kdLg",
      "tenant_region_scope": "NA",
      "tid": "72cc258e-****-****-****-************",
      "unique_name": "test@iamkdada.onmicrosoft.com",
      "upn": "test@iamkdada.onmicrosoft.com",
      "uti": "EOkD5S9tfEmWyYPgttdMAA",
      "ver": "1.0",
      "wids": [
        "b79fbf4d-****-****-****-************"
      ],
      "xms_cc": [
        "CP1"
      ],
      "xms_ssm": "1",
      "xms_st": {
        "sub": "sUBVrPYSLDEWp9Mkdw5eKxoTvXN8Vmda_WyPZPklqKU"
      },
      "xms_tcdt": 1679416726
    }.[Signature]
    

検証は以上の通りです。
実際に、セッションの無効化というイベントを発生させ、アクセス トークンが有効期限内であってもエラーを検知することができました。
なお、継続的アクセス評価ではイベントの伝搬時間に最大で 15 分の遅延が発生することがあります。
そのため、セッションの取り消しを行っても、即座にブロックされないことがありますため、その点についてご留意ください。

終わり

本記事では、 Digital Identity 技術勉強会 #iddance Advent Calendar 2023 の一環として、Open ID Foundation で標準化が進められているの SSF、CAEP、RISC の標準化についてと Microsoft Entra ID で CAEP 対応クライアント アプリの作成方法とその検証結果を紹介しました。
これらの概念は ID フェデレーションのセキュリティ課題への対応策として今後重要なものだと思っています。

ID フェデレーション技術は、シングルサインオンを通じてユーザーの利便性を高める一方で、セキュリティ上の脆弱性をもたらす可能性があります。
Microsoft Entra ID の継続的アクセス評価の検証結果を見ても、 SSF、CAEP、RISC はこれらの課題に対処し、より安全で効率的なデジタル アイデンティティ管理を可能にします。

この記事を通じて、読者の皆様が SSF、CAEP、RISC の重要性とそれらを活用した Microsoft Entra ID のセキュリティ機能についての理解を深めることができれば幸いです。

以下余談ですが、今回 Microsoft Entra ID と MS Graph 間の通信はブラック ボックスのため、確認することはできないですが、以下サイトで SSF で発生する通信について簡単に試せそうでしたので、もし気になる方は触ってみるのも楽しいかと思います。

https://caep.dev/generator

ではまた!

Discussion