---
# System prepended metadata

title: Microsoft ID で認証する WebAPI をテンプレートから作成する Bearer 2023-07-21
tags: [Web API, Microsoft ID, Bearer, Console, Microsoft Entra ID, ASP.NET Core]

---

---
lang: ja-jp
breaks: true
---
# 概要

![](https://hackmd.io/_uploads/SkV2Hvi52.png)


# WebAPI

## テンプレートからプロジェクトを作成

![](https://hackmd.io/_uploads/r1BAoIjc3.png)

![](https://hackmd.io/_uploads/Bkne2Isqn.png)

## csproj

```xml=
・・・
  <ItemGroup>
    <PackageReference Include="Microsoft.Identity.Web" Version="2.13.0" />
  </ItemGroup>
・・・
```

## appsettings.json

```json=
{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "WebAPIのアプリケーション (クライアント) ID",
    "Domain": "[Enter the domain of your tenant, e.g. contoso.onmicrosoft.com]",
    "TenantId": "WebAPIのディレクトリ (テナント) ID"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
```

## Program.cs

```csharp=
・・・
IConfigurationSection configuration = builder.Configuration.GetSection("AzureAd");

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(configuration);
・・・
```

## WeatherForecastController.cs

```csharp=
・・・
    [Authorize(Roles = "API.Console")]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
・・・
```

:::info
`[Authorize(Roles = "API.Console")]`を追加する。
"API.Console"は、`Azure Active Directory` にアプリロールとして追加された値を指定する。
* ![](https://hackmd.io/_uploads/S1R6dPw52.png)
* ![](https://hackmd.io/_uploads/rJcNtwv53.png)
* ![](https://hackmd.io/_uploads/rk-wFwwch.png)
:::

# Console

## テンプレートからプロジェクトを作成

![](https://hackmd.io/_uploads/rJgjC8i53.png)
![](https://hackmd.io/_uploads/ryZnRLj92.png)

## csproj

```xml=
・・・
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
    <PackageReference Include="Microsoft.Identity.Web" Version="2.13.0" />
  </ItemGroup>

  <ItemGroup>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
・・・
```

## appsettings.json

```json=
{
	"Instance": "https://login.microsoftonline.com/{0}",
	"Tenant": "コンソールアプリのディレクトリ (テナント) ID",
	"ClientId": "コンソールアプリのアプリケーション (クライアント) ID",
	"ClientSecret": "コンソールアプリのクライアントシークレットの値",
	"BaseAddress": "https://localhost:44372",
	"Scope": "WebAPIのアプリケーション ID の URI/.default",
	"Certificate": {}
}
```

## AuthenticationConfig.cs

```csharp=
using Microsoft.Identity.Web;

namespace Console;

public class AuthenticationConfig
{
    public string Instance { get; set; } = "https://login.microsoftonline.com/{0}";
    public string Tenant { get; set; } = "";
    public string ClientId { get; set; } = "";
    public string ClientSecret { get; set; } = "";
    public CertificateDescription? Certificate { get; set; } = null;
    public string BaseAddress { get; set; } = "";
    public string Scope { get; set; } = "";
}
```

## Program.cs

```csharp=
// See https://aka.ms/new-console-template for more information
using System.Diagnostics;
using System.Globalization;
using System.Net.Http.Headers;
using System.Text.Json.Nodes;
using Console;
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web;

System.Console.WriteLine("Hello, World!");

// Reads the configuration from a json file
AuthenticationConfig? config;
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json");
    IConfigurationRoot Configuration = builder.Build();
    config = Configuration.Get<AuthenticationConfig>();
}

if (config == null)
{ 
    throw new Exception("AuthenticationConfig is null");
}
System.Console.WriteLine(config);

// Even if this is a console application here, a daemon application is a confidential client application

Uri authorityUri = new Uri(String.Format(CultureInfo.InvariantCulture, config.Instance, config.Tenant));

IConfidentialClientApplication app = 
    ConfidentialClientApplicationBuilder.Create(config.ClientId)
        .WithClientSecret(config.ClientSecret)
        .WithAuthority(authorityUri)
        .Build();

// Add an in-memory well partitioned token cache to MSAL.NET confidential client application.
// Don't use this method in ASP.NET Core: rather use: services.AddInMemoryTokenCache() in ConfigureServices.
app.AddInMemoryTokenCache();

// With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the 
// application permissions need to be set statically (in the portal or by PowerShell), and then granted by
// a tenant administrator
string[] scopes = new string[] { config.Scope };

AuthenticationResult? result = null;
result = await app.AcquireTokenForClient(scopes)
    .ExecuteAsync();


if (result != null)
{
    System.Console.WriteLine($"[AccessToken]{result.AccessToken}");
    System.Console.WriteLine($"[ExpiresOn]{result.ExpiresOn.LocalDateTime.ToString()}");

    await CallWebApiAndProcessResultASync(
        $"{config.BaseAddress}/WeatherForecast",
        result.AccessToken,
        node => System.Console.WriteLine(node.ToString())
        );
}

static async Task CallWebApiAndProcessResultASync(
    string webApiUrl,
    string accessToken,
    Action<JsonNode> processResult
    )
{
    var httpClient = new HttpClient();

    if (!string.IsNullOrEmpty(accessToken))
    {
        var defaultRequestHeaders = httpClient.DefaultRequestHeaders;
        if (    defaultRequestHeaders.Accept == null
            || !defaultRequestHeaders.Accept.Any(m => m.MediaType == "application/json")
            )
        {
            httpClient.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json")
                );
        }
        defaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

        HttpResponseMessage response = await httpClient.GetAsync(webApiUrl)
            .ConfigureAwait(false);
        string content = await response.Content.ReadAsStringAsync()
            .ConfigureAwait(false);
        if (response.IsSuccessStatusCode)
        {
            string json = content;
            JsonNode? result = JsonNode.Parse(json);
            if (result != null)
            {
                processResult(result);
            }
        }
        else
        {
            Debug.WriteLine($"Failed to call the Web Api: {response.StatusCode}[{content}]");
        }
    }
}
```

:::info
`AcquireTokenForClient`で取得される`AccessToken`の有効期限は、`ExpiresOn`により取得できる。
※取得後１時間の日時が取得される。
:::



# 実行

![](https://hackmd.io/_uploads/Sy4gewo53.png)

![](https://hackmd.io/_uploads/BJ9SgPi5n.png)

