Azure AD B2CにCAPTCHA認証を導入する

Azure AD B2Cでは、Botによる攻撃を防ぐためにCAPTCHA認証を取り入れることができます。（2024/12現在プレビュー機能です）

https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-captcha?pivots=b2c-custom-policy

そこで、Azure AD B2Cのカスタムポリシーで、CAPTCHAを導入する手順および導入にあたってのポイントをまとめました。

## 前提
本記事で紹介するカスタムポリシーは、下記URLの「**Display Controls Starterpack/LocalAccounts**」のポリシーをベースとして説明します。

https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack

## カスタムポリシーの編集
カスタムポリシーXMLの構造に注意し、下記のドキュメント通りに`TrustFrameworkBase.xml`に定義を追加・更新していきます。

https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-captcha?pivots=b2c-custom-policy

## 動作確認
`TrustFrameworkBase.xml`, `TrustFrameworkLocalization.xml`, `TrustFrameworkExtensions.xml`, `SignUpOrSignin.xml`の順番にカスタムポリシーをアップロードし、サインアップを動作確認してみます。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398865/f1bb392c-429b-ab90-7f1a-0a5457cdccfe.png)

上記のように、メールアドレスの入力欄の後にCAPTCHAの欄が追加されています。


こちらの文字を間違えたままメールアドレスの確認コードを送信する「Send verification code」ボタンを押すとエラーが発生し、メールアドレス確認コードの送信ができないことが確認できます。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398865/67791697-6ad8-3f4b-c0d4-9cb80087ddef.png)

## ローカライズ
AD B2CのカスタムポリシーのLocalizationで、CAPTCHA入力欄のローカライズも可能です。

本サンプルの場合、`TrustFrameworkLocalization.xml`にローカライズの定義を追記します。

例えばCAPTCHAの画像上に表示するラベルの文言を表示する場合は下記の通りです。

```xml
<LocalizedResources Id="api.localaccountsignup.en">
  <LocalizedStrings>
    ...
    <LocalizedString ElementType="DisplayControl" ElementId="captchaControlChallengeCode" StringId="DisplayName">下記の画像に表示されている文字を入力してください</LocalizedString>
    ...
  </LocalizedStrings>
</LocalizedResources>
```

その他のローカライズについては下記ドキュメントをご参照ください。

参考：https://learn.microsoft.com/en-us/azure/active-directory-b2c/localization-string-ids#captcha-display-control-user-interface-elements

`TrustFrameworkLocalization.xml`を再アップロードし動作確認すると、下記の部分の文言が変更されています。

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398865/68a3cfcc-927e-a370-26af-f6c981f0d684.png)

## CAPTCHA導入にあたってのポイント/制限事項
### reCAPTCHAはAD B2Cデフォルトで利用できない（独自APIが必要）
CAPTCHAの他にもBotからの攻撃を防止する手法として、Googleが提供している[reCAPTCHA](https://developers.google.com/recaptcha?hl=ja)があるかと思いますが、Azure AD B2CではデフォルトでreCAPTCHAを利用できません。

ただし、reCAPTCHAのAPIを呼ぶカスタムAPIを独自で作成し、そちらをAD B2C側で呼び出すことでreCAPTCHAに対応させることも可能なようです。

下記がサンプルのGitHubとなります。

https://github.com/azure-ad-b2c/samples/tree/master/policies/captcha-integration


### ソーシャルアカウントを利用したサインアップでは未サポート
下記のドキュメントに記載されている通り、CAPTCHA認証が対応しているサインインフローはローカルアカウントでのサインアップのみとなります。

GoogleアカウントやFacebookなどのソーシャルアカウントでのサインアップではCAPTCHA認証を利用できません。

> You can enable this security feature in both sign-up and sign-in flows for your local accounts. CAPTCHA isn't applicable for social identity providers' sign-in.

https://learn.microsoft.com/ja-jp/azure/active-directory-b2c/add-captcha?pivots=b2c-custom-policy

### (JSカスタマイズしている場合)`<body>`タグ内で`<script>`タグが利用できなくなった
サインアップに関するページレイアウトのバージョンアップにより、`<body>`タグ内の`<script>`タグがB2C側でサニタイズされ利用できなくなりました。

**もし`<body>`タグに`<script>`タグを記載している場合は、それらを`<head>`タグに移行する必要があります。**

参考：https://learn.microsoft.com/ja-jp/azure/active-directory-b2c/page-layout#self-asserted-page-selfasserted

> XSS 攻撃を回避するためのスクリプト タグのサニタイズの強化。 このリビジョンでは、<body> 内のスクリプト タグがすべて破棄されます。 スクリプト タグは <head> タグに追加する必要があります。 

#### 変更前
```html
<head> ... </head>

<body>
  ...
  <script>
    ${function() {
        // 初回処理
    }};
  </script>
  ...
</body>
```

#### 変更後
```html
<head>
  ...
  <script>
    ${function() {
        // 初回処理
    }};
  </script>
  ...
</head>

<body> ... </body>
```


### (JSカスタマイズしている場合)`<script>`タグの移行により初回処理のタイミングが変更される

スクリプトを`<body>`から`<head>`タグ内に移行した場合は、スクリプト処理のタイミングが異なることに注意が必要です。

例えば、下記のようなDOM要素に依存する処理については、スクリプト実行時点はDOMが未生成のためエラーになってしまいます。

参考：https://penpen-dev.com/blog/script-inline-defer/

```html
<head>
  ...
  <script>
    ${function() {
        // メールアドレスのplaceholder変更
        $('#email').attr('placeholder', "メールアドレス");  // この時点ではid="email"のDOM要素が未生成のため、うまく処理されない
    }};
  </script>
  ...
</head>

<body> ... </body>
```

このような場合の改修例としては、下記の通りとします。

1. `<script>`タグ内を別JSファイルに切り出す
    ```js
    ${function() {
      // メールアドレスのplaceholder変更
      $('#email').attr('placeholder', "メールアドレス");
    }};
    ```

1. HTML側の`<script>`タグの`src`属性で上記のJSファイルを参照し、`defer`属性を追加する
    ```html
    <head>
      ...
      <script src="https://<ドメイン>/<JSファイル名>.js" defer></script>
      ...
    </head>

    <body> ... </body>
    ```

これで、下記の通り正しいタイミングでJSが実行できます。Emailのplaceholderが正しく変更できています。

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398865/81326767-d6a0-d1ad-232c-8949b764358f.png)


## 関連記事
[AD B2C記事まとめ](https://qiita.com/AsukaSeki/items/46afb6846cb4057b8050)