日本一詳しいAzure AD B2C カスタムポリシー開発ガイド

はじめに

こんにちは。NewITソリューション部 事業推進グループの永井です。

本記事では、Azure AD B2Cの「カスタムポリシー」について、筆者が開発するにあたって参考にした他サイトの記事や実際の開発事例と日本語での解説が乏しいTipsをまとめていきます。

記事の内容としては、既にカスタムポリシーを使っていて、「こういうケースに対応するにはどうすれば」と悩んでいる方向けですが、これから新しくカスタムポリシーを触る方にも、基本的な概念や参考になる構築手順の記事を紹介しているので、ぜひご活用いただけたら幸いです。

カスタムポリシーとは

Azure AD B2Cでは、サインイン/サインアップなどの認証機能やプロファイルの編集など、ユーザーがアプリケーションを操作するためのビジネスロジックを定義することができます。

定義する方法は「ユーザーフロー」と「カスタムポリシー」の2つがあり、ユーザーフローはMicrosoftが提供するテンプレートの処理を画面上で簡単に設定できる一方、細かいカスタマイズに対応できないことがあります。その点、カスタムポリシーではXML形式のコードで比較的自由に処理を定義できるため、高度なカスタマイズ要件がある場合でも対応可能です。

カスタムポリシーの「ポリシー」がサインイン/サインアップやプロファイル編集などの各機能のことを指し、開発したい機能ごとにポリシーを実装する必要があります。

参考ドキュメント

基本的な用語とファイル構成

カスタムポリシーはXML形式でコードを記述する点や、独自の用語が頻出する点で難しいと感じることが多いですが、一般的なプログラミング言語の考え方とリンクすることで理解しやすくなります。非常に参考になった他サイトの記事を引用しつつ解説します。

引用記事

用語について

カスタムポリシーの独自用語の意味をプログラミング言語の言葉で説明すると以下のように理解できます。

カスタムポリシーの用語意味
Claim変数
Claim Providerクラス
Technical Profileクラス内のメソッド
User Journeymain文
Orchestration StepUser Journey内で実行される各処理のステップ

これを踏まえて、ポリシー内の処理の流れを図示すると・・・

3.1 カスタムポリシーの処理の流れについて(『Azure AD B2C カスタムポリシーのスターターパック (LocalAccounts) の解説』より)

このようにポリシーの実行はUser Journeyで行われ、各Orchestration Stepの内容からTechnical Profileを辿っていけば一般的なプログラミング言語と同じように処理の流れを追うことができます。

ファイル構成について

カスタムポリシーは大きく4種類のファイルで構成されており、親子関係が存在します。カスタムポリシーには継承機能があり、親ファイルで定義した内容を子ファイルで継承し新しい要素を追加することでオーバーライドすることが可能です。Baseファイルを一番の親として上から順番に定義された内容を継承します。

継承モデル(『Azure AD B2C カスタム ポリシーの概要』より)

各ファイルの種類については以下の通りです。役割について、筆者が開発するにあたって定めた方針はハイライトにしています。

ファイルの種類役割ファイル名
Baseファイルカスタムポリシーに必要な共通処理を記載する。
Claimの宣言を行う。
TrustFrameworkBase.xml
Localizationファイル多言語対応に関する定義を記述する。TrustFrameworkLocalization.xml
ExtensionファイルBase ファイルでは足りない部分のカスタマイズを記述する。

RPファイルが多い場合は、Extensionファイルもその分だけ分割し、各ポリシー内で独自に使うTechnical ProfileとUser Journeyを記述する。
TrustFrameworkExtensions_ProfileEdit.xml、TrustFrameworkExtensions_PasswordReset.xmlなど
証明書利用者(RP)ファイルポリシー毎に作成する。
各ポリシーへアクセスするためのエンドポイント、アプリに返却するIDトークンの内容を記述する。
ProfileEdit.xml、PasswordReset.xmlなど

Extensionファイルはポリシー数(RPファイルの数)が多くなるとその分だけ記述量が増えるため、その際は1ファイルにすべて記述せずにRPファイルごとにExtensionファイルを分割して、各ポリシー内で独自に使うTechnical ProfileとUser Journeyを記述するようにしました。

開発のTipsと事例

ここからのセクションでは実際に開発するときのTipsや事例を紹介していきます。

開発を始めるにあたって

まず、カスタムポリシーの開発はサンプルコードがいくつか存在するためそれらを流用して実装を進めていきます。以下がそのサンプルコードのリポジトリです。

  • active-directory-b2c-custom-policy-starterpack
    • 標準的に利用できるスターターパック。用途に応じて以下のフォルダに分かれています。
      • LocalAccounts:ローカルアカウント(メールアドレスで登録するアカウント)のみを使用
      • SocialAccounts:ソーシャルアカウント(SNSアカウント連携で登録するアカウント)のみを使用
      • SocialAndLocalAccounts:ローカルアカウントとソーシャルアカウントの両方を使用
      • SocialAndLocalAccountsWithMfa:ローカルアカウントとソーシャルアカウント、多要素認証を使用
    • いずれのフォルダも、中身のポリシーはサインアップ/サインイン、プロファイル編集、パスワードリセットの3つで、基本的な処理が定義されています。
    • 筆者はこのサンプルコードをベースにして、別途求められている要件を追加実装して拡張していきました。
  • azure-ad-b2c/samples
    • 多岐にわたる種類のポリシーがまとめられています。
    • ただし、各ポリシーにはExtensionファイルやRPファイルしか用意がないため、既にカスタムポリシーを構築済みでこれからさらに拡張していくためのサンプルコードという位置づけです。

スターターパックを使った構築手順(記事紹介)

これから初めてカスタムポリシーの環境構築を行う方は、上記のようにスターターパックを基に構築することをおすすめします。以下にその構築手順が記載された記事を紹介します。

開発事例

次に実際に筆者が開発したカスタムポリシーの各要件について開発手順(記事紹介)やTipsをまとめていきます。以下の内容を紹介していきます。

  • 外部IdPを使用したサインアップ/サインイン
    • 本記事ではGoogleアカウント、およびAppleアカウントの連携
  • 画面文言の変更、多言語化
  • 利用規約の表示と同意済みかどうかの確認
  • カスタムHTMLによるUIの変更

外部IdPを使用したサインアップ/サインイン

外部のIdプロバイダー(ソーシャルアカウント連携)によるサインアップ/サインインの処理です。前のセクションで紹介したサンプルリポジトリに、ソーシャルアカウントを使用する場合のサンプルコードがあるためそちらをベースに実装します。(active-directory-b2c-custom-policy-starterpack/SocialAndLocalAccounts)

今回筆者が実装したのはGoogleアカウント、Appleアカウントとの連携です。カスタムポリシーで利用できる外部IdPについては、すべてMSの公式ドキュメントに具体的な手順が載っているのでそちらを紹介します。

Tips

  • どの外部IdP連携も、基本的な実装の流れは以下の通りです。
    1. 各外部IdPサービスでクライアントシークレットを払い出す
    2. クライアントシークレットを基にAzure portal上でポリシーキーを作成する
    3. カスタムポリシー側で外部IdP呼び出し用のTechnical Profileを追加する
    4. User Journeyに作成したTechnical Profileを追加する
  • Appleアカウント連携時のクライアントシークレットの署名について、公式ドキュメントではAzure関数を使ってJWTトークンを作成するよう指示がありますが、他のライブラリを使用しても構いません。筆者はローカルでC#のファイルを作成して該当箇所に値を入れて実行することでトークンを生成しました。
using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;

string audience = "https://appleid.apple.com";

string issuer = "<Your issuer>";
string subject = "<Your subject>";
string kid = "<Your kid>";
string p8key = "<Your p8key>";

IList<Claim> claims = new List<Claim> {
    new Claim ("sub", subject)
};

CngKey cngKey = CngKey.Import(Convert.FromBase64String(p8key), CngKeyBlobFormat.Pkcs8PrivateBlob);

SigningCredentials signingCred = new SigningCredentials(
    new ECDsaSecurityKey(new ECDsaCng(cngKey)),
    SecurityAlgorithms.EcdsaSha256
);

JwtSecurityToken token = new JwtSecurityToken(
    issuer,
    audience,
    claims,
    DateTime.Now,
    DateTime.Now.AddDays(180),
    signingCred
);
token.Header.Add("kid", kid);
token.Header.Remove("typ");

JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();

string jwt = tokenHandler.WriteToken(token);
Console.WriteLine(jwt);

以上の手順を踏むことで、サインイン画面で各外部IdPを選択し、認証を通すことができます。

画面文言の変更、多言語化

Localizationファイルで画面上に表示する文言を定義することで、デフォルトの文言から変更を加えたり、多言語化対応が可能です。以下のサンプルコードを例に説明します。

  • LocalizedResources : Id=でどの画面のどの言語についての設定かを指定するタグ。
    • api.signuporsignin.enであればサインイン/サインアップ画面の英語版を指します。
  • LocalizedStrings : 各文言をまとめるタグ。
  • LocalizedString : 1つの文言を定義するタグ。
    • 画面に出す実際の文言をタグで囲む。文言を変更するときは、この文字列を変更します。
    • ElementType : その画面項目の種類を指します。ユーザー属性はClaimType、単なる画面上の文言ならUxElement、エラーメッセージの場合はErrorMessage等。
    • ElementId : ElementType が ClaimType に設定されている場合に使用されます。
    • StringId : 特定の画面項目を指すID。

StringIdはMS公式ドキュメントに一覧があり、この中の一覧から設定したい文言を選んで以下のサンプルコードのように記載する。(『ローカライズ文字列』)

<LocalizedResources Id="api.signuporsignin.en">
  <LocalizedStrings>
    <LocalizedString ElementType="ClaimType" ElementId="signInName" StringId="DisplayName">Email Address</LocalizedString>
    <LocalizedString ElementType="UxElement" StringId="heading">Sign in</LocalizedString>
    <LocalizedString ElementType="UxElement" StringId="social_intro">Sign in with your social account</LocalizedString>
    <LocalizedString ElementType="UxElement" StringId="local_intro_generic">Sign in with your {0}</LocalizedString>
    <!-- 中略 -->
  </LocalizedStrings>
</LocalizedResources>

<LocalizedResources Id="api.signuporsignin.ja">
  <LocalizedStrings>
    <LocalizedString ElementType="ClaimType" ElementId="signInName" StringId="DisplayName">メール アドレス</LocalizedString>
    <LocalizedString ElementType="UxElement" StringId="social_intro">ソーシャル アカウントでサインイン</LocalizedString>
    <LocalizedString ElementType="ClaimType" ElementId="password" StringId="DisplayName">パスワード</LocalizedString>
    <LocalizedString ElementType="ErrorMessage" StringId="DefaultMessage">メールアドレスまたはパスワードが正しくありません。</LocalizedString>
    <LocalizedString ElementType="ErrorMessage" StringId="UserMessageIfClaimsPrincipalDoesNotExist">メールアドレスまたはパスワードが正しくありません。</LocalizedString>
  </LocalizedStrings>
</LocalizedResources>

<LocalizedResources Id="api.signuporsignin.zh-hans">
  <LocalizedStrings>
    <LocalizedString ElementType="ClaimType" ElementId="signInName" StringId="DisplayName">电子邮件地址</LocalizedString>
    <LocalizedString ElementType="UxElement" StringId="social_intro">使用社交帐户登录</LocalizedString>
    <LocalizedString ElementType="ClaimType" ElementId="password" StringId="DisplayName">密码</LocalizedString>
    <LocalizedString ElementType="ErrorMessage" StringId="DefaultMessage">电子邮件地址或密码不正确。</LocalizedString>
    <LocalizedString ElementType="ErrorMessage" StringId="UserMessageIfClaimsPrincipalDoesNotExist">电子邮件地址或密码不正确。</LocalizedString>
  </LocalizedStrings>
</LocalizedResources>

<LocalizedResources Id="api.signuporsignin.zh-hant">
  <LocalizedStrings>
    <LocalizedString ElementType="ClaimType" ElementId="signInName" StringId="DisplayName">電子郵件位址</LocalizedString>
    <LocalizedString ElementType="UxElement" StringId="social_intro">使用您的社交帳戶登入</LocalizedString>
    <LocalizedString ElementType="ClaimType" ElementId="password" StringId="DisplayName">密碼</LocalizedString>
    <LocalizedString ElementType="ErrorMessage" StringId="DefaultMessage">請輸入您的密碼。</LocalizedString>
    <LocalizedString ElementType="ErrorMessage" StringId="UserMessageIfClaimsPrincipalDoesNotExist">請輸入您的密碼。</LocalizedString>
  </LocalizedStrings>
</LocalizedResources>

利用規約の表示と規約同意済みかどうかの確認

ユーザーが最新の利用規約に同意しないとサインアップ/サインインできないようにすることが可能です。

MSの公式ドキュメントでは具体的なロジックの説明が記載されていますが一部のみです。実際のサンプルコードの全容は先述のリポジトリのうち、samples/policies/sign-in-sign-up-versioned-tou-with-socialにあります。

Tips

  • 利用規約のページのリンクをカスタムポリシーの画面上に表示することができます。
  • 利用規約がアップデートされた際はカスタムポリシー内でtermsOfUseTextUpdateDateTimeの値を直接書き換えて更新する必要があります。
<InputParameters>
      <!--Valueの値を変更する-->
      <InputParameter Id="termsOfUseTextUpdateDateTime" DataType="dateTime" Value="2025-01-15T00:00:00" />
</InputParameters>
  • 利用規約のチェックボックスのローカライズは以下のように設定します。
<LocalizedCollections>
          <LocalizedCollection ElementType="ClaimType" ElementId="extension_termsOfUseConsentChoice" TargetCollection="Restriction">
            <Item Text="利用規約に同意する" Value="AgreeToTermsOfUseConsentYes" SelectByDefault="false" />
          </LocalizedCollection>
</LocalizedCollections>

カスタムHTMLによるUIの変更

カスタムポリシーのXMLファイルそれ自体は画面UIの定義が入っていませんが、別途HTMLを作成しAzureのストレージサービスに配置、そのHTMLをカスタムポリシーが参照するように設定すれば画面UI自体の変更を加えることができ、例えばアプリケーションのブランドイメージに合わせたカスタムポリシーを作成することも可能です。

こちらもHTMLのテンプレートがサンプルとして存在するのでこれらをベースにしながら画面のUIを変更することをおすすめします。こちらもHTMLのテンプレートがサンプルとして存在するのでこれらをベースにしながら画面のUIを変更することをおすすめします。

azure-ad-b2c/html-templatesのフォルダ構成は以下のようになっています。

├── AzureBlue                     // 青を基調としたHTMLテンプレート
│   ├── exception.html
│   ├── idpSelector.html
│   ├── multifactor-1.0.0.html
│   ├── selfAsserted.html
│   └── unified.html
├── MSA                           // 白を基調としたHTMLテンプレート
│   ├── exception.html
│   ├── idpSelector.html
│   ├── multifactor-1.0.0.html
│   ├── selfAsserted.html
│   └── unified.html
├── classic                       // クラシックのHTMLテンプレート
│   ├── exception.html
│   ├── idpSelector.html
│   ├── multifactor-1.0.0.html
│   ├── selfAsserted.html
│   ├── unified.html
│   └── updateProfile.html
└── src
    ├── backgrounds               // 画面背景の格納フォルダ
    │   ├── 1-1.png
    │   └── 4-2.png
    ├── classic                   // クラシックHTMLテンプレートで使用するイラスト画像の格納フォルダ
    │   └── img
    │       └── classic_signin_illustration_optimized.png
    ├── fonts                     // フォント
    │   ├── segoeui.WOFF
    │   └── segoeui_bold.WOFF
    ├── idp_logos                 // 外部IdPのロゴ
    │   ├── colored
    │   │   ├── amazon.svg
    │   │   ├── apple.svg
    │   │   ├── facebook.svg
    │   │   ├── github.svg
    │   │   ├── google.svg
    │   │   ├── linkedin.svg
    │   │   ├── local.svg
    │   │   ├── microsoft.svg
    │   │   ├── qq.svg
    │   │   ├── twitter.svg
    │   │   ├── wechat.svg
    │   │   └── weibo.svg
    │   └── white
    │       ├── amazon.svg
    │       ├── apple.svg
    │       ├── facebook.svg
    │       ├── github.svg
    │       ├── google.svg
    │       ├── linkedin.svg
    │       ├── local.svg
    │       ├── microsoft.svg
    │       ├── qq.svg
    │       ├── twitter.svg
    │       ├── wechat.svg
    │       └── weibo.svg
    └── images                    // アイコン
        ├── left-arrow.svg
        └── logo.svg

上記テンプレートを変更後に、公式ドキュメントの『2.Azure BLOB ストレージ アカウントを作成する』以降の手順を踏むことでカスタムHTMLによるUIの変更ができます。

Tips

まとめ

以上で、Azure AD B2Cのカスタムポリシーの概念から構築手順、そしてカスタマイズ事例とそのTipsについてご紹介しました。認証周りの実装を自分で一から実装するとなるとそれなりの工数が予想されますが、このようにテンプレートやサンプルコードを流用しながら割と複雑な要件にも対応できてしまうのがカスタムポリシーの魅力かと思います。

一度分かってしまえば理解しやすいのですが、カスタムポリシーは現状日本語での解説があまり充実していないため、本記事でまとめた事項が皆さんの役に立てたら幸いです。

弊社では、クラウドやAIを中心としたシステムを提供しております。

ご興味をお持ちいただけましたらぜひお気軽にお問合せください。

 

いいね (←参考になった場合はハートマークを押して評価お願いします)
読み込み中...

注意事項・免責事項

※技術情報につきましては投稿日時点の情報となります。投稿日以降に仕様等が変更されていることがありますのでご了承ください。

※公式な技術情報の紹介の他、当社による検証結果および経験に基づく独自の見解が含まれている場合がございます。

※これらの技術情報によって被ったいかなる損害についても、当社は一切責任を負わないものといたします。十分な確認・検証の上、ご活用お願いたします。

※当サイトはマイクロソフト社によるサポートページではございません。パーソルクロステクノロジー株式会社が運営しているサイトのため、マイクロソフト社によるサポートを希望される方は適切な問い合わせ先にご確認ください。
 【重要】マイクロソフト社のサポートをお求めの方は、問い合わせ窓口をご確認ください