Azure AD B2C のカスタムポリシーでスターターパックをそのまま使ってみた

2023.12.30

いわさです。

先日 Azure AD B2C にて API コネクタを使うことでカスタム属性への自動設定を行うことが出来るようになりました。

しかし、デフォルトで用意されているサインアップ・サインインのユーザーフローでは、カスタム属性をユーザー属性として設定しない場合は API コネクタで設定が出来ず、ユーザー属性として設定した場合はユーザー入力をさせる形となってしまいます。

何も表示せずにカスタム属性を自動設定するというのをやりたいのですが、「表示名」についてはユーザー属性に指定しない場合もデフォルトで「unknown」という値が設定されるようになり、API コネクタでカスタム値での更新が出来ました。

組み込みの属性とカスタム属性では扱いが違う場合があるので必ずしも出来るとは言い切れないのですが、上記挙動からすると頑張ればカスタム属性の入力をさせずに関数側での自動設定が出来るのではと感じました。
そこで本日はユーザーフローをカスタマイズするための「カスタムポリシー」を触ってみたいと思います。

今回は初めてカスタムポリシーを触るということで、カスタムポリシーの HelloWorld 的なものを少し設定変更してみて挙動を観察し、その後スターターパックを使ってみました。

カスタムポリシーの導入

事前定義されているユーザーフローはいくつか種類が用意されており、さらにそれぞれのユーザーフローで軽くカスタマイズ設定の余地があります。ただし、これらで要件を満たせない時は、カスタムポリシーが必要になります。

イメージとしては事前定義された組み込みのポリシーがユーザーフローで、完全にカスタマイズ可能なポリシーがカスタムポリシーです。

カスタムポリシーはテナントの「Identity Experience Framework」メニューからカスタムポリシーの定義ファイルをアップロードすることで作成することが出来ます。

まずはどういうものなのか基本を知るということで、Hello World が用意されているのでこちらをまずは試してみたいと思います。

サンプルの Hello World テンプレートはBuildingBlocksClaimsProvidersUserJourneysRelyingPartyから構成された XML ファイルです。
それぞれのブロックのリファレンスは以下です。

そう、カスタムポリシーを使いこなすためには、この独自定義ファイルについてイチから学ぶ必要があります。
これは...なかなか骨が折れますね!

今回は次のようにユーザー属性に存在しない適当なクレームを宣言し、出力させるようにしてみました。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <TrustFrameworkPolicy
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
      PolicySchemaVersion="0.3.0.0"
      TenantId="hoge1227org.onmicrosoft.com"
      PolicyId="B2C_1A_ContosoCustomPolicy"
      PublicPolicyUri="http://hoge1227org.onmicrosoft.com/B2C_1A_ContosoCustomPolicy">

        <BuildingBlocks>
            <!-- Building Blocks Here-->
            <ClaimsSchema>
                <ClaimType Id="hoge1">
                    <DisplayName>hoge1</DisplayName>
                    <DataType>string</DataType>
                </ClaimType>        
                <ClaimType Id="hoge2">
                    <DisplayName>hoge2</DisplayName>
                    <DataType>string</DataType>
                </ClaimType>
              </ClaimsSchema>
        </BuildingBlocks>

        <ClaimsProviders>
            <!-- Claims Providers Here-->
            <ClaimsProvider>
                <DisplayName>Token Issuer</DisplayName>
                <TechnicalProfiles>
                  <TechnicalProfile Id="JwtIssuer">
                    <DisplayName>JWT Issuer</DisplayName>
                    <Protocol Name="None" />
                    <OutputTokenFormat>JWT</OutputTokenFormat>
                    <Metadata>
                      <Item Key="client_id">{service:te}</Item>
                      <Item Key="issuer_refresh_token_user_identity_claim_type">hoge1</Item>
                      <Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
                    </Metadata>
                    <CryptographicKeys>
                      <Key Id="issuer_secret" StorageReferenceId="B2C_1A_hoge1229signinkey" />
                      <Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_hoge1229enryptkey" />
                    </CryptographicKeys>
                  </TechnicalProfile>
                </TechnicalProfiles>
              </ClaimsProvider>

              <ClaimsProvider>
                <!-- The technical profile(s) defined in this section is required by the framework to be included in all custom policies. -->
                <DisplayName>Trustframework Policy Engine TechnicalProfiles</DisplayName>
                <TechnicalProfiles>
                  <TechnicalProfile Id="TpEngine_c3bd4fe2-1775-4013-b91d-35f16d377d13">
                    <DisplayName>Trustframework Policy Engine Default Technical Profile</DisplayName>
                    <Protocol Name="None" />
                    <Metadata>
                      <Item Key="url">{service:te}</Item>
                    </Metadata>
                  </TechnicalProfile>
                </TechnicalProfiles>
              </ClaimsProvider>
        </ClaimsProviders>

        <UserJourneys>
            <!-- User Journeys Here-->
            <UserJourney Id="HelloWorldJourney">
                <OrchestrationSteps>
                  <OrchestrationStep Order="1" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
                </OrchestrationSteps>
              </UserJourney>
        </UserJourneys>

        <RelyingParty>
            <!-- 
                Relying Party Here that's your policy’s entry point
                Specify the User Journey to execute 
                Specify the claims to include in the token that is returned when the policy runs
            -->
            <DefaultUserJourney ReferenceId="HelloWorldJourney"/>
            <TechnicalProfile Id="HelloWorldPolicyProfile">
                <DisplayName>Hello World Policy Profile</DisplayName>
                <Protocol Name="OpenIdConnect" />
                <OutputClaims>
                <OutputClaim ClaimTypeReferenceId="hoge1" PartnerClaimType="sub" DefaultValue="hoge1defaultvalue"/>
                <OutputClaim ClaimTypeReferenceId="hoge2" DefaultValue="hoge2defaultvalue"/>
                </OutputClaims>
                <SubjectNamingInfo ClaimType="sub" />
            </TechnicalProfile>
        </RelyingParty>
    </TrustFrameworkPolicy>

カスタムポリシーもユーザーフローと同様に Azure ポータル上から実行することが可能です。
先程のカスタムポリシー定義ファイルをアップロードし実行してみます。

ユーザー操作は特になく、リダイレクト URI へカスタムクレームが設定された JWT が送信されました。

スターターパックを使ってみる

カスタムポリシーは一から作成することも出来ますが、GitHub 上の次のリポジトリでスターターパックという形でサンプルのポリシーが公開されているのでそちらをベースに開発することが推奨されています。

上記リポジトリのファイルをカスタマイズして使う必要があるので、ローカルダウンロードあるいはフォークなりしておきます。

こちらのリポジトリのポリシーはほぼデフォルトで動作します。
今回は LocalAccounts フォルダ配下のSignUpOrSignin.xmlを動作させてみたいと思います。

こちらのSignUpOrSignin.xmlですが、次のような継承関係があるので、計 4 つのカスタムポリシーをTrustFrameworkBase.xmlから順にアップロードする必要があります。

SignUpOrSignin.xml └ TrustFrameworkExtensions.xml └ TrustFrameworkLocalization.xml └ TrustFrameworkBase.xml

ちなみに、ユーザーフローからポリシー定義ファイルをダウンロードすることも出来るのですが、base-v1というものがベースポリシーに指定されており、カスタムポリシーでこれを使おうとすると使用出来ないわよというエラーになります。

<br /><?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TrustFrameworkPolicy
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
  PolicySchemaVersion="0.3.0.0"
  TenantId="hoge1227org.onmicrosoft.com"
  PolicyId="B2C_1A_signup_signin"
  PublicPolicyUri="http://hoge1227org.onmicrosoft.com/B2C_1A_signup_signin">

  <BasePolicy>
    <TenantId>hoge1227org.onmicrosoft.com</TenantId>
    <PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
  </BasePolicy>
:

アップロード出来たら、B2C_1A_SIGNUP_SIGNINカスタムポリシーを実行してみます。

ユーザーポリシーと同じようなサインイン&サインアップ画面が表示されました。

英語ですね。これロケールが多分考慮されてないですね。

日本語化してみた

ユーザーフローだとRelyingParty.UserJourneyBehaviors.ContentDefinitionParametersui_localesが設定されています。
カスタムポリシーの場合だと、次のページに記述されているようにLocalizationが必要そうです。

SighUpOrSigin.xmlに以下を追記してみたところ日本語での表記となりました。

SighUpOrSigin.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TrustFrameworkPolicy
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
  PolicySchemaVersion="0.3.0.0"
  TenantId="hoge1227org.onmicrosoft.com"
  PolicyId="B2C_1A_signup_signin"
  PublicPolicyUri="http://hoge1227org.onmicrosoft.com/B2C_1A_signup_signin">

  <BasePolicy>
    <TenantId>hoge1227org.onmicrosoft.com</TenantId>
    <PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
  </BasePolicy>

  <BuildingBlocks>
    <Localization Enabled="true">
      <SupportedLanguages DefaultLanguage="ja" MergeBehavior="ReplaceAll">
        <SupportedLanguage>en</SupportedLanguage>
        <SupportedLanguage>ja</SupportedLanguage>
      </SupportedLanguages>
    </Localization>
  </BuildingBlocks>

  <RelyingParty>
    <DefaultUserJourney ReferenceId="SignUpOrSignIn" />
    <Endpoints>
:

なるほどなぁ。

サインアップしてみる

続いて、サインアップを行ってみましょう。
Hello World の時はサインアップ手順なしでいきなり JWT が生成されていました。

サインアップ画面に遷移するとユーザーフローの時と同じようにメールアドレスのコード検証から属性情報の入力まで行われます。

ユーザーも普通に作成され、トークンも次のように設定されていました。

さいごに

本日は Azure AD B2C のカスタムポリシーがどういうものなのか確認し、その後スターターパックをとりあえずそのまま使ってみました。

いやぁこれは、ユーザーフローかカスタムポリシーかで Azure AD B2C の選定判断が変わるくらい大きな分岐になりそうですね。柔軟に色々出来そうだなという反面学習コストが気になるところ。

カスタム属性に関して言うと、カスタムポリシーで頑張るよりも Microsoft Graph API でカスタム属性を設定するか、あるいはトークン生成時にも API コネクタを統合出来るので、そのタイミングで別のデータストアで管理するカスタム属性をクレームに追加するかでも良いかなと思い始めて来ました。
API の実行量がちょっと気になるところではありますが。

カスタムポリシーの習得に向けて色々やりたいのですが、SaaS on Azure の観点で他にも検証したいことがあるので Azure AD B2C についてはここまでで一旦お休みしようと思います。次回はコンピューティングリソース周りのマルチテナンシーを見ていきたいなと。