Azure AD B2Cのカスタムポリシーを使えば、REST APIの呼び出しにより独自のビジネスロジックを一連の認証フローの中で行うことができます。
REST API呼び出しのチュートリアルは下記の通りです。
チュートリアル:Azure Active Directory B2C で REST API 要求の交換をカスタム ポリシーに追加する
ただし上記チュートリアルのドキュメントに下記の記載がある通り、チュートリアルのコードのままですとB2C側は保護されていないREST APIに接続できてしまいます。
お使いの RESTful API を実稼働用にセキュリティで保護する方法については、RESTful API のセキュリティ保護に関するページを参照してください。
そのため、実運用する際はREST APIの保護を行うようB2C側とAPI側両方に対応が必要です。
REST APIの保護についてはいくつか方法がありますが、今回はAPIキー認証の方法をご紹介します。
- 認証方法の詳細についてはこちらの公式ドキュメントをご参照ください。
この記事のターゲット
- Azure AD B2Cで独自のREST API呼び出しを利用される方
- Azure AD B2Cを実運用するアプリに組み込みたい方
- ASP .NET CoreでREST API開発をされている方
- 本記事で紹介する認証方法はHTTPリクエストヘッダー内のAPIキーを照合する一般的なAPIキー認証のため、Azure FunctionsやJava Springなど他のREST APIフレームワークでも対応できます。
この記事のゴール
- B2C~REST API間のAPIキー認証を利用するための設定およびカスタムポリシーを変更する。
- ASP .NET Coreで、APIキーの認可処理を行う。
- APIキー認証のエラー時に、エラーメッセージを返す。
前提
- B2Cではカスタムポリシーを使用します。
- B2Cの認証フローの定義方法にはユーザーフローとカスタムポリシーの2つがあり、MS公式ではユーザーフローの利用が推奨されています。
- ただし、独自のREST APIの組み込みはカスタムポリシーのみのサポートであるため、今回はそちらを使用します。
- REST API側のフレームワークはASP .NET Core 3.1を使用します。
B2C側の設定
B2C側では、ポリシーキーの追加とカスタムポリシーの変更を行います。
詳細なドキュメントはこちらの公式ドキュメントをご参照ください。
ポリシーキーを作成する
下記の通り、B2Cの設定画面でポリシーキーを作成します。
キャプチャのapi-key-value
の箇所にAPIキーを設定します。
作成後、ポリシーキー一覧画面にこのように追加されます。設定した名前にB2C_1A_
プレフィックスが付与されます。
このキー名はカスタムポリシーで使用するため名前をあらかじめコピーしておきます。
カスタムポリシーを変更する
REST API呼び出し部分のTechnicalProfile
を変更します。
例として、以下のソースコードをベースに改修していきます。
<TechnicalProfile Id="TestApi">
<Metadata>
<Item Key="ServiceUrl">https://xxx.example.com/api/auth/user</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
...
</TechnicalProfile>
変更ポイントは下記の3つです。
-
Metadata
のAuthenticationType
をApiKeyHeader
に変更します。 -
Metadata
のAllowInsecureAuthInProduction
をfalse
に変更します。 -
CryptographicKeys
を追加し、APIキーを下記の通り設定します。-
Id
:ヘッダーのキー名 -
StorageReferenceId
:ポリシーキー名- 「ポリシーキーを作成する」で作成したキー名を設定します。
- 下記コードの例ですと、REST API呼び出し時
x-api-key
ヘッダーにB2C_1A_RestApiKey
ポリシーキーの値を格納します。
-
<TechnicalProfile Id="TestApi">
<Metadata>
<Item Key="ServiceUrl">https://xxx.example.com/api/auth/user</Item>
<Item Key="AuthenticationType">ApiKeyHeader</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AllowInsecureAuthInProduction">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="x-api-key" StorageReferenceId="B2C_1A_RestApiKey" />
</CryptographicKeys>
...
</TechnicalProfile>
REST API側の設定(ASP .NET Core 3.1)
B2Cから受け取ったリクエストヘッダー内のAPIキーを照合し、既定値と異なる場合はエラーを返します。
これを実現するには、独自の認可処理を行うAttributeを作成し、B2Cのリクエストを受け付けるAPIコントローラーに適用させます。
また、ASP .NET Coreの標準ライブラリのみを利用するため、別途ライブラリのインポートは不要です。
-
ApiKeyAuthorizeFilterクラス
- IAuthorizationFilterをオーバーライドし、独自の認可用フィルターを作成します。
- B2C側から受け取ったリクエストヘッダーを照合し、既定値と異なる場合はエラーを返します。
- ポイントはエラーメッセージの返し方で、
ApiKeyError()
メソッドでB2C規定の形式でエラーレスポンスで返すようにしています。- 本来認証エラーは
401 Unauthorized
として返しますが、B2Cでサポートされているエラーレスポンスは409 Conflict
のためConflictObjectResult
を返しています。 - その他、エラーレスポンスの詳細についてはこちらをご参照ください。
- 本来認証エラーは
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Configuration; using System; using System.Linq; using System.Net; namespace SampleApi.Auth.Filters { public class ApiKeyAuthorizeFilter : IAuthorizationFilter { public IConfiguration Configuration { get; } /// <summary> /// APIキーのリクエストヘッダー名 /// </summary> public static readonly string ApiKey = "x-api-key"; public ApiKeyAuthorizeFilter(IConfiguration configuration) { Configuration = configuration; } /// <summary> /// 認可処理 /// </summary> /// <param name="context"></param> public void OnAuthorization(AuthorizationFilterContext context) { // リクエストヘッダーのAPIキーを元に認可処理 var apiKeys = context.HttpContext.Request.Headers[ApiKey]; if (apiKeys.Contains(Configuration["AuthorizedKey"])) { return; } // 既定値と一致しない場合はエラーを返す context.Result = ApiKeyError(); } /// <summary> /// APIキー不正エラーメッセージを返す /// </summary> /// <returns></returns> private ConflictObjectResult ApiKeyError() { return new ConflictObjectResult(new { Version = "1.0.0", Status = HttpStatusCode.Conflict, UserMessage = "APIKeyが不正です" }); } } }
-
ApiKeyAuthorizeAttributeクラス
- TypeFilterAttributeをオーバーライドし、コントローラーで使うAttributeと上記で作成した認可処理と紐づけます。
- コンストラクタの中身は何も記述しない状態で大丈夫です。
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Configuration; using System; using System.Linq; using System.Net; namespace SampleApi.Auth.Filters { public class ApiKeyAuthorizeAttribute : TypeFilterAttribute { public ApiKeyAuthorizeAttribute() : base(typeof(ApiKeyAuthorizeFilter)) { } } }
-
API Controllerクラス
- B2Cのリクエストを受け付けるControllerクラスに上記で作成したAttributeを追加します。
[Route("api/[controller]")] [ApiController] [ApiKeyAuthorize] // 追加 public class AuthenticateController : ControllerBase { ... }
-
Startup.cs
- 認可処理を行うため
Startup.cs
のConfigure()
メソッドにUseAuthentication
ミドルウェアを追加します。 - 下記のコードの場合、追加する場所は
UseRouting
とUseAuthorization
の間になります。- ミドルウェアの順番の詳細についてはこちらをご参照ください。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); // 追加 app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
- 認可処理を行うため
-
appsettings.json
- 最後に、アプリケーション設定として「ポリシーキーの作成」で設定したAPIキーを追加します。
- キー名は
ApiKeyAuthorizeFilter
クラスのConfiguration
に対応したキー名にします。
{ "AuthorizedKey": "api-key-value" }
動作確認
REST APIを呼び出すB2Cのフローを動作確認してみます。
検証用に、リダイレクトURIは公式のB2Cチュートリアルにも記載されているhttps://jwt.ms
とします。
正しく認証が通ればトークンが表示されます。
- まず正常系を確認します。認証が完了し、トークンが返ってきました。
- 次に異常系。REST API側のAPIキーを別の値に設定してみます。
まとめ
- 今回はASP .NET Coreを使用したAzure AD B2C~REST API間のAPIキー認証をご紹介しました。
- REST APIの呼び出しについては、以前Qiitaに記事を投稿しております。そちらも合わせてご覧いただければと思います。