次の方法で共有


チュートリアル: Microsoft ID プラットフォームを使用して ASP.NET Core Web API を構築してセキュリティで保護する

適用対象: 白いチェック マーク記号が付いた緑の円。 従業員テナント 白いチェック マーク記号が付いた緑の円。 外部テナント (詳細はこちら)

このチュートリアル シリーズでは、Microsoft ID プラットフォームを使用して ASP.NET Core Web API を保護し、承認されたユーザーとクライアント アプリにのみアクセスできるようにする方法について説明します。 作成する Web API では、委任されたアクセス許可 (スコープ) とアプリケーションのアクセス許可 (アプリ ロール) の両方が使用されます。

このチュートリアルでは、次の操作を行います。

  • ASP.NET Core Web API を構築する
  • Microsoft Entra アプリの登録の詳細を使用するように Web API を構成する
  • Web API エンドポイントを保護する
  • Web API を実行して HTTP 要求を受け付けていることを確認する

前提条件

新しい ASP.NET Core Web API プロジェクトを作成する

最小限の ASP.NET Core Web API プロジェクトを作成するには、次の手順に従います。

  1. Visual Studio Code またはその他のコード エディターでターミナルを開き、プロジェクトを作成するディレクトリに移動します。

  2. .NET CLI またはその他のコマンド ライン ツールで次のコマンドを実行します。

    dotnet new web -o TodoListApi
    cd TodoListApi
    
  3. ダイアログ ボックスで作成者を信頼するかどうかを確認するメッセージが表示されたら、[ はい ] を選択します。

  4. 必要なアセットをプロジェクトに追加するかどうかを確認するダイアログ ボックスが表示されたら、[ はい ] を選択します。

必要なパッケージをインストールする

ASP.NET Core Web API をビルド、保護、テストするには、次のパッケージをインストールする必要があります。

  • Microsoft.EntityFrameworkCore.InMemory- Entity Framework Core とメモリ内データベースを使用できるパッケージ。 これはテスト目的で役立ちますが、運用環境用には設計されていません。
  • Microsoft.Identity.Web - Microsoft ID プラットフォームと統合する Web アプリと Web API への認証と承認のサポートの追加を簡略化する一連の ASP.NET Core ライブラリ。

パッケージをインストールするには、次のコマンドを使用します。

dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

アプリ登録の詳細を構成する

アプリ フォルダーで appsettings.json ファイルを開き、Web API の登録後に記録したアプリ登録の詳細を追加します。

{
    "AzureAd": {
        "Instance": "Enter_the_Authority_URL_Here",
        "TenantId": "Enter_the_Tenant_Id_Here",
        "ClientId": "Enter_the_Application_Id_Here"
    },
    "Logging": {...},
  "AllowedHosts": "*"
}

次のプレースホルダーを以下のように置き換えてください。

  • Enter_the_Application_Id_Here をアプリケーション (クライアント) ID に置き換えます。
  • Enter_the_Tenant_Id_Here をディレクトリ (テナント) ID に置き換えます。
  • 次のセクションで説明するように、 Enter_the_Authority_URL_Here を機関の URL に置き換えます。

アプリの権限 URL

機関 URL は、Microsoft Authentication Library (MSAL) がトークンを要求できるディレクトリを指定します。 次に示すように、従業員と外部テナントの両方で異なる方法で構築します。

//Instance for workforce tenant
Instance: "https://login.microsoftonline.com/"

カスタム URL ドメインを使用する (省略可能)

カスタム URL ドメインは、従業員テナントではサポートされていません。

アクセス許可を追加する

クライアント アプリがユーザーのアクセス トークンを正常に取得するために、すべての API は少なくとも 1 つのスコープ (委任されたアクセス許可とも呼ばれます) を発行する必要があります。 また、API は、クライアント アプリがアクセス トークンを自分で取得するために、つまりユーザーがサインインしていない場合に、少なくとも 1 つのアプリ ロール (アプリケーションのアクセス許可とも呼ばれます) を発行する必要があります。

これらのアクセス許可は、appsettings.json ファイルで指定します。 このチュートリアルでは、次の委任されたアクセス許可とアプリケーションのアクセス許可を登録しました。

  • 委任されたアクセス許可:ToDoList.ReadToDoList.ReadWrite
  • アプリケーションのアクセス許可:ToDoList.Read.AllToDoList.ReadWrite.All

ユーザーまたはクライアント アプリケーションが Web API を呼び出すと、これらのスコープまたはアクセス許可を持つクライアントのみが、保護されたエンドポイントへのアクセスを承認されます。

{
  "AzureAd": {
    "Instance": "Enter_the_Authority_URL_Here",
    "TenantId": "Enter_the_Tenant_Id_Here",
    "ClientId": "Enter_the_Application_Id_Here",
    "Scopes": {
      "Read": ["ToDoList.Read", "ToDoList.ReadWrite"],
      "Write": ["ToDoList.ReadWrite"]
    },
    "AppPermissions": {
      "Read": ["ToDoList.Read.All", "ToDoList.ReadWrite.All"],
      "Write": ["ToDoList.ReadWrite.All"]
    }
  },
  "Logging": {...},
  "AllowedHosts": "*"
}

API に認証と承認を実装する

認証と承認を構成するには、 program.cs ファイルを開き、その内容を次のコード スニペットに置き換えます。

認証スキームを追加する

この API では、既定の認証メカニズムとして JSON Web トークン (JWT) ベアラー スキームを使用します。 JWT ベアラー スキームを登録するには、 AddAuthentication メソッドを使用します。

// Add required packages to your imports
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Add an authentication scheme
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration);

アプリのモデルを作成する

プロジェクトのルート フォルダーに、 Models という名前のフォルダーを作成します。 Models フォルダーに移動し、ToDo.csという名前のファイルを作成し、次のコードを追加します。

using System;

namespace ToDoListAPI.Models;

public class ToDo
{
    public int Id { get; set; }
    public Guid Owner { get; set; }
    public string Description { get; set; } = string.Empty;
}

上記のコードでは、 ToDo という名前のモデルが作成されます。 このモデルは、アプリが管理するデータを表します。

データベース コンテキストの追加

次に、データ モデルの Entity Framework 機能を調整するデータベース コンテキスト クラスを定義します。 このクラスは、アプリケーションとデータベース間の相互作用を管理する Microsoft.EntityFrameworkCore.DbContext クラスを継承します。 データベース コンテキストを追加するには、次の手順に従います。

  1. プロジェクトのルート フォルダーに DbContext という名前のフォルダーを作成します。

  2. DbContext フォルダーに移動し、ToDoContext.csという名前のファイルを作成し、次のコードを追加します。

    using Microsoft.EntityFrameworkCore;
    using ToDoListAPI.Models;
    
    namespace ToDoListAPI.Context;
    
    public class ToDoContext : DbContext
    {
        public ToDoContext(DbContextOptions<ToDoContext> options) : base(options)
        {
        }
    
        public DbSet<ToDo> ToDos { get; set; }
    }
    
  3. プロジェクトのルート フォルダー内の Program.cs ファイルを開き、次のコードで更新します。

    // Add the following to your imports
    using ToDoListAPI.Context;
    using Microsoft.EntityFrameworkCore;
    
    //Register ToDoContext as a service in the application
    builder.Services.AddDbContext<ToDoContext>(opt =>
        opt.UseInMemoryDatabase("ToDos"));
    

前のコード スニペットでは、DB コンテキストをスコープ付きサービスとして ASP.NET Core アプリケーション サービス プロバイダー (依存関係挿入コンテナーとも呼ばれます) に登録します。 また、ToDo List API のメモリ内データベースを使用するように ToDoContext クラスを構成します。

コントローラーを設定する

コントローラーは通常、リソースを管理するための作成、読み取り、更新、および削除 (CRUD) アクションを実装します。 このチュートリアルでは、API エンドポイントの保護に重点を置いているため、コントローラーには 2 つのアクション項目のみを実装します。 すべての To-Do 項目を取得するための Read all アクションと、新しい To-Do 項目を追加する作成アクション。 プロジェクトにコントローラーを追加するには、次の手順に従います。

  1. プロジェクトのルート フォルダーに移動し、Controllers という名前のフォルダーを作成 します

  2. ToDoListController.cs フォルダー内に という名前のファイルを作成し、次のボイラー プレート コードを追加します。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;
using ToDoListAPI.Models;
using ToDoListAPI.Context;

namespace ToDoListAPI.Controllers;

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ToDoListController : ControllerBase
{
    private readonly ToDoContext _toDoContext;

    public ToDoListController(ToDoContext toDoContext)
    {
        _toDoContext = toDoContext;
    }

    [HttpGet()]
    [RequiredScopeOrAppPermission()]
    public async Task<IActionResult> GetAsync(){...}

    [HttpPost]
    [RequiredScopeOrAppPermission()]
    public async Task<IActionResult> PostAsync([FromBody] ToDo toDo){...}

    private bool RequestCanAccessToDo(Guid userId){...}

    private Guid GetUserId(){...}

    private bool IsAppMakingRequest(){...}
}

コントローラーにコードを追加する

このセクションでは、前のセクションでスキャフォールディングされたコントローラーにコードを追加する方法について説明します。 ここでは、API を構築するのではなく、保護することに重点を置きます。

  1. 必要なパッケージをインポートします。Microsoft.Identity.Web パッケージは、トークン検証の処理などの認証ロジックを簡単に処理するのに役立つ、MSAL.NET のラッパーです。 エンドポイントで承認が必要になるように、組み込みの Microsoft.AspNetCore.Authorization パッケージを使用します。

  2. この API を呼び出すアクセス許可は、ユーザーの代わりに委任されたアクセス許可か、ユーザーの代わりではなくクライアントからそれ自体として呼び出すアプリケーションのアクセス許可を使用して付与したので、呼び出しがアプリによってアプリのために行われているかどうかを把握することが重要です。 これを行う最も簡単な方法は、アクセス トークンにオプションの要求 idtyp 含まれているかどうかを調べる方法です。 この idtyp 要求が、API がトークンがアプリ トークンかアプリ + ユーザー トークンかを判断するための最も簡単な方法です。 idtyp オプション要求を有効にすることをお勧めします。

    idtyp 要求が有効になっていない場合は、roles および scp 要求を使用して、アクセス トークンがアプリ トークンかアプリ + ユーザー トークンかを判断できます。 Microsoft Entra ID によって発行されたアクセス トークンには、2 つの要求のうち少なくとも 1 つがあります。 ユーザーに発行されたアクセス トークンは、scp 要求を含みます。 アプリケーションに発行されたアクセス トークンは、roles 要求を含みます。 両方の要求を含むアクセス トークンはユーザーに対してのみ発行され、scp 要求は委任されたアクセス許可を指定し、roles 要求はユーザーのロールを指定します。 どちらも持たないアクセス トークンは受け入れられません。

    private bool IsAppMakingRequest()
    {
        if (HttpContext.User.Claims.Any(c => c.Type == "idtyp"))
        {
            return HttpContext.User.Claims.Any(c => c.Type == "idtyp" && c.Value == "app");
        }
        else
        {
            return HttpContext.User.Claims.Any(c => c.Type == "roles") && !HttpContext.User.Claims.Any(c => c.Type == "scp");
        }
    }
    
  3. 行われている要求に、目的のアクションを実行するのに十分なアクセス許可が含まれているかどうかを決定するヘルパー関数を追加します。 アプリが自身のための要求を行っているのか、またはアプリが特定のリソースを所有するユーザーに代わって呼び出しを行っているのかをユーザー ID を検証することで確認します。

    private bool RequestCanAccessToDo(Guid userId)
        {
            return IsAppMakingRequest() || (userId == GetUserId());
        }
    
    private Guid GetUserId()
        {
            Guid userId;
            if (!Guid.TryParse(HttpContext.User.GetObjectId(), out userId))
            {
                throw new Exception("User ID is not valid.");
            }
            return userId;
        }
    
  4. アクセス許可の定義を組み込んでルートを保護します。 [Authorize] 属性をコントローラー クラスに追加することで、API を保護します。 これによって、認可されている ID で API が呼び出された場合にのみコントローラー アクションを呼び出すことができるようになります。 アクセス許可の定義では、これらのアクションを実行するために必要なアクセス許可の種類を定義します。

    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ToDoListController: ControllerBase{...}
    

    GET エンドポイントと POST エンドポイントにアクセス許可を追加します。 Microsoft.Identity.Web.Resource 名前空間の一部である RequiredScopeOrAppPermission メソッドを使用してこれを行います。 次に、RequiredScopesConfigurationKey 属性および RequiredAppPermissionsConfigurationKey 属性を使用して、このメソッドにスコープとアクセス許可を渡します。

    [HttpGet]
    [RequiredScopeOrAppPermission(
        RequiredScopesConfigurationKey = "AzureAD:Scopes:Read",
        RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Read"
    )]
    public async Task<IActionResult> GetAsync()
    {
        var toDos = await _toDoContext.ToDos!
            .Where(td => RequestCanAccessToDo(td.Owner))
            .ToListAsync();
    
        return Ok(toDos);
    }
    
    [HttpPost]
    [RequiredScopeOrAppPermission(
        RequiredScopesConfigurationKey = "AzureAD:Scopes:Write",
        RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Write"
    )]
    public async Task<IActionResult> PostAsync([FromBody] ToDo toDo)
    {
        // Only let applications with global to-do access set the user ID or to-do's
        var ownerIdOfTodo = IsAppMakingRequest() ? toDo.Owner : GetUserId();
    
        var newToDo = new ToDo()
        {
            Owner = ownerIdOfTodo,
            Description = toDo.Description
        };
    
        await _toDoContext.ToDos!.AddAsync(newToDo);
        await _toDoContext.SaveChangesAsync();
    
        return Created($"/todo/{newToDo!.Id}", newToDo);
    }
    

コントローラーを使用するように API ミドルウェアを構成する

次に、HTTP 要求を処理するためにコントローラーを認識して使用するようにアプリケーションを構成します。 program.cs ファイルを開き、次のコードを追加して、コントローラー サービスを依存関係挿入コンテナーに登録します。


builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();

app.Run();

前のコード スニペットでは、 AddControllers() メソッドは、必要なサービスを登録してコントローラーを使用するようにアプリケーションを準備しますが、 MapControllers() は、着信 HTTP 要求を処理するためにコントローラー ルートをマップします。

API を実行する

コマンド dotnet runを使用して、API を実行してエラーが発生しないようにします。 テスト中でも HTTPS プロトコルを使用する場合は、信頼する必要があります 。NET の開発証明書

  1. ターミナルで次のように入力して、アプリケーションを起動します。

    dotnet run
    
  2. ターミナルには、次のような出力が表示されます。これは、アプリケーションが http://localhost:{port} で実行され、要求をリッスンしていることを確認します。

    Building...
    info: Microsoft.Hosting.Lifetime[0]
        Now listening on: http://localhost:{port}
    info: Microsoft.Hosting.Lifetime[0]
        Application started. Press Ctrl+C to shut down.
    ...
    

Web ページ http://localhost:{host} には、次の図のような出力が表示されます。 これは、API が認証なしで呼び出されているためです。 承認された呼び出しを行うには、次の 手順 を参照して、保護された Web API にアクセスする方法に関するガイダンスを参照してください。

Web ページが起動したときの 401 エラーを示すスクリーンショット。

この API コードの完全な例については、サンプル ファイルを参照してください。

次のステップ