Azure Active Directory(Azure AD)の v2.0 は Microsoft ID プラットフォームと呼ばれていて、OpenID Connect 準拠のエンドポイントが提供されていたり、アクセス許可を一度にまとめて確認するでのはなく、必要に応じてアクセス許可を確認(増分および動的な同意)できたりします。
ただし、Azure AD v1.0 のすべての機能が v2.0 で提供されているわけではないので、注意が必要です。
docs.microsoft.com
今回は、ASP.NET Core で作った Web アプリケーションと Web API に Azure AD v2.0 認証を組み込み、フロントエンドの Web アプリケーションからバックエンドの Web API を呼び出してみます。
基本的にはこちらのサンプル通りですが、必要最低限のコードで試してみました。
github.com
事前準備
Visual Studio 2019 のプロジェクトテンプレートを使って、ASP.NET Core 3.1 で Web アプリケーション(MVC)と Web API のプロジェクトを作成しておきます。
テンプレートで認証を設定すると、Azure AD v1.0 向けの Active Directory Authentication Library(ADAL)が使われるので、今回は指定しません。
Azure AD v2.0 向けの Microsoft Authentication Library(MSAL)を手動で組み込みます。現時点のバージョンは 0.3.1-preview となっており、プレビュー版です。
docs.microsoft.com
Azure AD にアプリケーションを登録する
Azure Portal で Azure AD のアプリケーションを2つ登録します。
- フロントエンドの Web アプリケーション
- バックエンドの Web API
必須項目 | 設定値 |
Redirect URIs | https://localhost:xxxxx/signin-oidc(Web アプリと Web API の URL) |
Implicit grant | ID tokens にチェックをいれる |
Client secrets | 自動生成(後から確認できないのでメモしておく) |
バックエンドの Web API のみ、Expose an API メニューから Application ID URI
と Scopes
を登録します。
フロントエンドの Web アプリケーションを作る
Web アプリケーションのプロジェクトに対して、MSAL の NuGet パッケージをインストールします。
PM>Install-Package Microsoft.Identity.Web.UI -Version 0.3.1-preview
Azure AD のアプリケーションに登録した情報をもとに、appsettings.json に AzureAd
のセクションを追加します。
{ "AzureAd": { "Instance": "https://login.microsoftonline.com/", "Domain": "[テナントのドメインを入力します。e.g. contoso.onmicrosoft.com]", "TenantId": "[Directory (Tenant) ID を入力します。]", "ClientId": "[Application (Client) ID を入力します。]", "CallbackPath": "/signin-oidc", "ClientSecret": "[Client secrets を入力します。]" }, }
Startup クラスにおいて、MSAL の初期化処理を実装します。
public void ConfigureServices(IServiceCollection services) { // appsettings.json の AzureAd セクションから構成を読み取り、認証サービスを構成します。 services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAd") .EnableTokenAcquisitionToCallDownstreamApi() .AddInMemoryTokenCaches(); services.AddHttpClient(); services.AddControllersWithViews() .AddMicrosoftIdentityUI(); // アカウント管理ページを追加 }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // (ここまでのコードは省略) app.UseRouting(); app.UseAuthentication(); // パイプラインに認証ミドルウェアを追加 app.UseAuthorization(); // (これ以降のコードは省略) }
HomeController クラスには、Authorize
属性を指定します。
[Authorize] public class HomeController : Controller { }
ログインしたユーザー名とサインアウトをヘッダーに追加しておきます。Views/Sharedディレクトリに _LoginPartial.cshtml を追加し、_Layout.cshtml に組み込みます。
asp-area には、MicrosoftIdentity
を指定します。
@using System.Security.Principal <ul class="navbar-nav"> @if (User.Identity.IsAuthenticated) { <li class="nav-item"> <span class="navbar-text text-dark">Hello @User.Identity.Name!</span> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignOut">Sign out</a> </li> } else { <li class="nav-item"> <a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in</a> </li> } </ul>
以上で、フロントエンドの Web アプリケーションは実装完了です。Visual Studio からデバッグ実行して、動作確認してみます。
バックエンドの Web API を作る
Web API のプロジェクトに対して、MSAL の NuGet パッケージをインストールします。
PM>Install-Package Microsoft.Identity.Web -Version 0.3.1-preview
Azure AD のアプリケーションに登録した情報をもとに、appsettings.json に AzureAd
のセクションを追加します。
Web アプリケーションと同様ですが、API なので CallbackPath
は必要ありません。
{ "AzureAd": { "Instance": "https://login.microsoftonline.com/", "Domain": "[テナントのドメインを入力します。e.g. contoso.onmicrosoft.com]", "TenantId": "[Directory (Tenant) ID を入力します。]", "ClientId": "[Application (Client) ID を入力します。]", "ClientSecret": "[Client secrets を入力します。]" }, }
Startup クラスにおいて、MSAL の初期化処理を実装します。
public void ConfigureServices(IServiceCollection services) { // appsettings.json の AzureAd セクションから構成を読み取り、認証サービスを構成します。 services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd"); services.AddControllers(); }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // (ここまでのコードは省略) app.UseRouting(); app.UseAuthentication(); // パイプラインに認証ミドルウェアを追加 app.UseAuthorization(); // (これ以降のコードは省略) }
WeatherForecastController クラスには、Authorize
属性を指定します。
[Authorize] [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { }
以上で、バックエンドの Web API は実装完了です。Visual Studio からデバッグ実行して、Postman で動作確認してみます。
Postmanを使ったアクセストークンの取得は、こちらの記事を参照してください。
gooner.hateblo.jp
フロントエンドからバックエンドの Web API を呼び出す
Azure AD のアプリケーションを変更する
フロントエンドの Web アプリケーションに対して、バックエンドの Web API を呼び出せるアクセス権を追加します。
API のアクセス権は、add a permission をクリックして選択できます。忘れずに、Grant admin consent for 既定のディレクトリ
も設定しておきます。
フロントエンドの Web アプリケーションを変更する
Web アプリケーションのプロジェクトに対して、appsettings.json に CallApi
のセクションを追加します。
{ "CallApi": { "Scope": "[Web API の Scope を入力します。e.g. api://<GUID>/user_impersonation]", "BaseAddress": "[Web API の URL を入力します。e.g. https://localhost:xxxxx]" } }
Startup クラスにおいて、MSAL の初期化処理にバックエンドの Web API の Scope を追加します。EnableTokenAcquisitionToCallDownstreamApi
メソッドの引数に Scope を渡します。
public void ConfigureServices(IServiceCollection services) { // バックエンドの Web API の Scope を追加 services.AddMicrosoftIdentityWebAppAuthentication(Configuration) .EnableTokenAcquisitionToCallDownstreamApi(new string [] { Configuration["CallApi:Scope"], }) .AddInMemoryTokenCaches(); }
バックエンドの Web API を呼び出すアクセストークンを取得したいので、HomeController クラスのコンストラクタで ITokenAcquisition
のインスタンスを DI コンテナから受け取ります。
[Authorize] public class HomeController : Controller { private readonly ITokenAcquisition _tokenAcquisition; private readonly IHttpClientFactory _clientFactory; private readonly string _webApiScope = string.Empty; private readonly string _webApiBaseAddress = string.Empty; public HomeController(ITokenAcquisition tokenAcquisition, IConfiguration configuration, IHttpClientFactory clientFactory) { _tokenAcquisition = tokenAcquisition; _clientFactory = clientFactory; _webApiScope = configuration["CallApi:Scope"]; _webApiBaseAddress = configuration["CallApi:BaseAddress"]; } }
Privacy ページがクリックされた際に、バックエンドの Web API を呼び出します。アクションメソッドには、AuthorizeForScopes
属性でバックエンドの Web API の Scope を付与します。
[AuthorizeForScopes(ScopeKeySection = "CallApi:Scope")] public async Task<IActionResult> Privacy() { // アクセストークンを取得する var scopes = new string[] { _webApiScope }; var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes); // バックエンドの Web API を呼び出し var client = _clientFactory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await client.GetAsync($"{_webApiBaseAddress}/weatherforecast"); if (response.StatusCode == HttpStatusCode.OK) { ViewData["ApiResult"] = await response.Content.ReadAsStringAsync(); } else { ViewData["ApiResult"] = $"Invalid status code in the HttpResponseMessage: {response.StatusCode}."; } return View(); }
実行結果
Azure AD で認証されたフロントエンドの Web アプリケーションから、バックエンドの API を呼び出せることを確認します。
まとめ
ASP.NET Core で作った Web アプリケーションと Web API に Azure AD v2.0 認証を組み込み、フロントエンドの Web アプリケーションからバックエンドの Web API を呼び出してみました。
認証ライブラリの MSAL はまだプレビュー版ですし、もうすぐ .NET 5 がリリースされるタイミングなので、継続してキャッチアップしていきたいと思います。
今回のソースコードは、こちらで公開しています。
github.com