LoginSignup
0
1

More than 1 year has passed since last update.

Azure AD B2C×ASP .NET CoreのAPIキー認証

Last updated at Posted at 2021-02-26

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キーを設定します。
B2C1.png
作成後、ポリシーキー一覧画面にこのように追加されます。設定した名前にB2C_1A_プレフィックスが付与されます。
このキー名はカスタムポリシーで使用するため名前をあらかじめコピーしておきます。
B2C2.png

カスタムポリシーを変更する

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つです。

  • MetadataAuthenticationTypeApiKeyHeaderに変更します。
  • MetadataAllowInsecureAuthInProductionfalseに変更します。
  • 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.csConfigure()メソッドにUseAuthenticationミドルウェアを追加します。
    • 下記のコードの場合、追加する場所はUseRoutingUseAuthorizationの間になります。
      • ミドルウェアの順番の詳細についてはこちらをご参照ください。
    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とします。
正しく認証が通ればトークンが表示されます。

  • まず正常系を確認します。認証が完了し、トークンが返ってきました。
    • ※セキュリティの都合上ぼかしを入れていますが、実際にはトークンの文字列が続きます。
      B2C3.png
  • 次に異常系。REST API側のAPIキーを別の値に設定してみます。
    • 認証フローを実行すると、下記の通りエラーが返ってきました。ソースコードで記載したエラーメッセージも表示されています。
      B2C4.png

まとめ

関連記事

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1