Azure AD B2C カスタムポリシー
はじめに
IDサービスである Azure Active Directory B2C のカスタムポリシーを利用する方法についてまとめてみました。
一般的な利用方法についてはこちらの記事を参照ください。
カスタムポリシー とは
Azure Active Directory B2C (以下、AAD B2C と記載する)のテナントの動作を定義する構成ファイルでXMLで記述されます。
簡易的に動作させるものはユーザーフローですが、カスタマイズが必要なものについてはカスタムポリシーを編集して実装します。
Open ID Connect、OAuth、SAMLや REST API を実装することができ、必要に応じた構成要素を組むことで様々な要件に対応することができます。
また、標準的に利用できる Azure AD B2C カスタムポリシー スターターパックも公開されており、以下のシーンに対しての最小構成のポリシーが提供されています。
Local Accounts : ローカルアカウントのみ使用する場合
Social Accounts : ソーシャルアカウントのみを使用する場合
SosiacAnd LocalAccounts : ローカルアカウントとソーシャルアカウント両方使用する場合
SocialAndLocalAccountsWithMFA : ローカルアカウント、ソーシャルアカウント及び多要素認証オプションを使用する場合
サンプルコードも公開されおり、いくつかのシナリオで構成されたサンプルが利用できます。
カスタムポリシーの展開
カスタムポリシーを展開するには以前ご紹介したページのクライアントシークレットの作成まで実施しておく必要があります。
ユーザーフローの作成部分がカスタムポリシーに置き換わるイメージです。
本稿では以下のローカルアカウントを使用するという前提においてシナリオについて検証していきます。
サインアップおよびサインインフロー
ローカルアカウントでサインアップする
ローカルアカウントでサインインする
パスワードのリセット
※ カスタムポリシーで操作できない属性値も存在するため、必要に応じて Graph API を使用して属性値の変更をします。
事前に必要な準備
以前ご紹介させていただいた記事の「クライアントシークレットの作成」まで実施した状態のテナントが必要です。
また、カスタムポリシーでローカルアカウントのサインアップとサインインを実装するには以下のアプリケーションを登録する必要があります。
IentityExperienceFramework (Web API)
ProxyIdentityExperienceFramework (ネイティブアプリ)
IdentityExperienceFramework アプリケーションの作成
アプリケーションを登録するには[Identity Experience Framework]から追加します。
続いて[ポリシーキー]を開きます。
[追加]を選択します。
以下指定をして作成を選択します。
オプション : 生成
名前 : TokenSigningKeyContainer
キーのタイプ : RSA
キー使用法 : 署名
さらに追加を選択します。
以下指定をして作成を選択します。
オプション : 生成
名前 : TokenEncryptionKeyContainer
キーのタイプ : RSA
キー使用法 : 暗号化
アプリケーションを登録します。
[アプリの登録]から[新規登録]を選択します。
以下指定をして登録を選択します。
表示名:IdentityExperienceFramework
APIのアクセス:この組織ディレクトリのみに含まれるアカウント
リダイレクトURI:Web / https://tenantname.b2clogin.com/tenantname.onmicrosoft.com
アクセス許可:チェックをいれる
tenantname は自テナント名を代入してください。
作成できたらアプリケーションIDを控えておきます。
次に[APIの公開]から[Scopeの追加]を選択します。
内容はそのままで[保存してから続ける]を選択します。
以下指定をしてスコープの追加を選択します。
スコープ名:user_impersonation ← 任意の文字列で問題なし、URLになる
表示名:任意
説明:任意
状態:有効
API の公開に追加されていることを確認します。
ProxyIdentityExperienceFramework アプリケーションの作成
[アプリの登録]から[新規登録]を選択します。
以下指定をして登録を選択します。
表示名:ProxyIdentityExperienceFramework
APIのアクセス:この組織ディレクトリのみに含まれるアカウント
リダイレクトURI:パブリッククライアント/ネイティブクライアント / myapp://auth
アクセス許可:チェックをいれる
作成できたらアプリケーションIDを控えておきます。
[認証]から[次のモバイルとデスクトップのフローを有効にする]を[はい]へ変更して[保存]します。
[マニフェスト]から allowPublicClient が true になっていることを確認します。
true ではない場合は、true へ変更します。
[API のアクセス許可]から[アクセス許可の追加]を選択します。
[所属する組織で使用しているAPI]から[IdentitryExperienceFramework]を選択します。
アクセス許可に表示されているものにチェックを入れて[アクセス許可の追加]を選択します。
一覧に追加されたことを確認し、[{テナント名}に管理者の同意を与えます]を選択します。
確認が出るので[はい]を選択します。
一覧で状態がグリーンのチェックになっていることを確認して完了です。
ローカルアカウントでサインアップする
前述したスターターパックから LocalAccounts に含まれる以下のものを使います。
PaaswordReset.xml → 証明書利用者ファイル
ProfileEdit.xml → 証明書利用者ファイル
SignUpOrSignin.xml → 証明書利用者ファイル
TrustFrameworkBase.xml → ベースファイル
TrustFrameworkExtensions.xml → 拡張ファイル
TrustFrameworkLocalization.xml → ローカライズファイル
修正点としては以下となります。
すべてのファイル
TenantId
PublicPolicyUri
【変更前】
TenantId="yourtenant.onmicrosoft.com"
PublicPolicyUri="http://yourtenant.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">
[yourtenant]となっている部分を自テナントへ書き換えます。
テナント名の確認方法は Azure ポータルから[Azure AD B2C]を開くと以下画面が表示されるので、黒塗り部分を確認します。
テナント名が[abcdef]だと以下のようになります。
【変更後】
TenantId="abcdef.onmicrosoft.com"
PublicPolicyUri="http://abcdef.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">
TrustFrameworkBase.xml以外
【変更前】
<BasePolicy>
<TenantId>yourtenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
前述と同様に[yourtenant]となっている部分を自テナントへ書き換えます。
【変更後】
<BasePolicy>
<TenantId>abcdef.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
TrustFrameworkExtensions.xml
Metadata の client_id → 27行目
Metadata の client_id → 28行目
InputClamis の ClaimTypeReferenceId="client_id" → 31行目
InputClamis の ClaimTypeReferenceId="resource_id" → 32行目
【変更前】
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<Item Key="client_id">ProxyIdentityExperienceFrameworkAppId</Item>
<Item Key="IdTokenAudience">IdentityExperienceFrameworkAppId</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="ProxyIdentityExperienceFrameworkAppId" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="IdentityExperienceFrameworkAppId" />
</InputClaims>
</TechnicalProfile>
[Metadata]内にある client_id と [InputClaims]内にある client_id の[ProxyIdentityExperienceFrameworkAppId]部分を前項で作成した ProxyIdentityExperienceFramework のアプリケーションIDで上書きします。
また、[Metadata]内にある IdTokenAudience と [InputClaims]内にある resource_id の[IdentityExperienceFrameworkAppId]部分を前項で作成した IdentityExperienceFrameworkAppId のアプリケーションIDで上書きします。
以下はマスクしたIDで記載した例となります。
【変更後】
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<Item Key="client_id">b1bb11bb-1bbb-111b-1b11-bb11bbbbb1b1</Item>
<Item Key="IdTokenAudience">0aa111aa-111a-1111-a111-1111111a1111</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="b1bb11bb-1bbb-111b-1b11-bb11bbbbb1b1" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="0aa111aa-111a-1111-a111-1111111a1111" />
</InputClaims>
</TechnicalProfile>
保存したら Azure AD B2C の [Identity Experience Framework] の[カスタムポリシーをアップロードします]から以下の順でアップロードします。
TrustFrameworkBase.xml
TrustFrameworkLocalization.xml
TrustFrameworkExtensions.xml
SignUpOrSignin.xml
ProfileEdit.xml
PasswordReset.xml
ここまでで以下の機能が有効化されます。
サインアップ
サインイン
動作確認
カスタムポリシーからサインアップの動作確認を行います。
B2C_1A_SIGNUP_SIGNIN を選択します。
アプリケーションの選択で[webapp1]を選択して[今すぐ実行]をクリックします。
サインイン画面が表示されるので[Sign up now]をクリックします。
画面に沿ってメールアドレス認証を行い、認証コードを入力後パスワード等を入力して[Create]をクリックします。
以下のような画面が出るとユーザー作成成功です。
Azure AD B2C のユーザー画面から追加されていることを確認します。
パスワードリセットを実装する
TrustFrameworkExtensions.xml を編集します。
スターターパックで展開した場合、BuildingBlocks 要素は空になっているので、[パスワードを忘れた場合]のリンクを選択した場合の技術プロファイルへ移動させるため以下のようにクレームを追加します。
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="isForgotPassword">
<DisplayName>isForgotPassword</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Whether the user has selected Forgot your Password</AdminHelpText>
</ClaimType>
</ClaimsSchema>
</BuildingBlocks>
次に ClaimsProviders へ isForgotPassword クレームを追加します。
初期で ClaimsProvider が1つ入っているので下へ追記します。
<ClaimsProviders>
<ClaimsProvider>
~~~~~~
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="ForgotPassword">
<DisplayName>Forgot your password?</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isForgotPassword" DefaultValue="true" AlwaysUseDefaultValue="true"/>
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<Metadata>
<Item Key="setting.forgotPasswordLinkOverride">ForgotPasswordExchange</Item>
</Metadata>
</TechnicalProfile>
<TechnicalProfile Id="LocalAccountWritePasswordUsingObjectId">
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
次に SubJourney を追加します。
初期では存在しないのでコメントされている UserJourneys の後へ以下のように追加します。
<!--UserJourneys>
</UserJourneys-->
<SubJourneys>
<SubJourney Id="PasswordReset" Type="Call">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
</OrchestrationSteps>
</SubJourney>
</SubJourneys>
</TrustFrameworkPolicy>
最後に UserJourney を追加します。
TrustFrameworkBase.xml の875行目から910行目の UserJourney Id ="SignUpOrSignIn" をコピーして、TrustFrameworkExtensions.xml の UserJourney セクションへ貼り付けます。
UserJourney ID は[CustomSignUpOrSignIn]と変更しておきます。(同一名称のIDを共存できないため)
また、TrustFrameworkExtensions.xml の UserJourney はコメントされているので外しておきます。
OrchestrationStep Order="1" の ClaimsProviderSelections に以下を追加します。
<ClaimsProviderSelection TargetClaimsExchangeId="ForgotPasswordExchange" />
さらに次の OrchestrationStep の ClaimsExchanges セクションへ以下を追加します。
<ClaimsExchange Id="ForgotPasswordExchange" TechnicalProfileReferenceId="ForgotPassword" />
次の OrchestrationStep との間に以下を追加します。
Order が追加されるので既存の 3 と 4 は 4 と 5 へ変更します。
<OrchestrationStep Order="3" Type="InvokeSubJourney">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>isForgotPassword</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<JourneyList>
<Candidate SubJourneyReferenceId="PasswordReset" />
</JourneyList>
</OrchestrationStep>
最終的に出来上がった UserJourneys は以下となります。
<UserJourneys>
<UserJourney Id="CustomSignUpOrSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="ForgotPasswordExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
<ClaimsExchange Id="ForgotPasswordExchange" TechnicalProfileReferenceId="ForgotPassword" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="InvokeSubJourney">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>isForgotPassword</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<JourneyList>
<Candidate SubJourneyReferenceId="PasswordReset" />
</JourneyList>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
UserJourney を追加したので、証明書利用者セクションから呼び出すよう変更を行います。
SignUpOrSignin.xml の17行目の呼び出す UserJourney を[CustomSignUpOrSignIn]へ変更します。
<DefaultUserJourney ReferenceId="CustomSignUpOrSignIn" />
また、OutputClaims セクションへ以下を追加します。
<OutputClaim ClaimTypeReferenceId="isForgotPassword" DefaultValue="false" />
保存して TrustFrameworkExtensions.xml と SignUpOrSignin.xml をアップロードしたら実装完了です。
下図のようにカスタムポリシーが既に存在する場合は上書きしますにチェックを入れることを忘れずにアップロードします。
動作確認
前述の動作確認手順と同様に、サインイン画面を表示して[Forgot your password?]を選択して、パスワードのリセットができるか確認します。
まとめ
如何でしたでしょうか。
利用する認証プロバイダを自身で選択しつつ、ユーザーの画面遷移をカスタムポリシーで制御することで認証フローの要件に対応することができます。
例えばサインアップの際に利用規約画面を閲覧させた上でサインアップさせたり、初期パスワードはユーザーに入力させるが初回サインイン時にはパスワードを変更させるなどの挙動も作成することが出来ます。
また、メール認証は初期では Azure AD B2C 側で用意されたメールプロバイダを使用されますが、SendGrid へ変更することで送信元アドレスの変更や送信するメール本文の変更なども可能です。
このような認証の仕組みを利用することでアプリケーション側では認証トークンの処理のみ実装することで、開発工数の削減にも繋げられます。
今回は画面のテンプレートは変更せず、デフォルトのものを使用していますが認証画面もカスタマイズしたものを使用できますので、アプリケーションのデザインのテンプレートを用意することで一貫した画面デザインにすることも可能です。
カスタムドメインも組み合わせることで、ユーザーには Azure AD B2C を使っていることを意識させずにサービス化することが可能となります。
このような仕組みを使ったアプリケーションデザインも検討してみてはいかがでしょうか。
今回作成したカスタムポリシーのサンプルは下記に公開しています。