mTLS Proof-of-Possession (mTLS PoP) を使用してトークン バインディングを構成する

Note

この機能は現在プライベート プレビュー段階であるため、すべてのクライアントが mTLS PoP 証明書を取得できるわけではありません。

証明書トークン バインド (mTLS PoP - 相互 TLS 所有証明とも呼ばれます) は、アクセス トークンを特定の X.509 証明書に暗号でバインドする高度なセキュリティ機能です。 RFC 8705 では、このバインディングについて説明します。 このバインディングにより、トークンが傍受された場合でも、攻撃者は対応する秘密キーを所有せずにトークンを使用できなくなります。

トークン バインドのしくみを理解する

次の手順では、取得から検証までのトークン バインド フローについて説明します。

  1. トークン取得: トークン バインドが有効になっているアクセス トークンを要求する場合、Microsoft Identity Web はトークン要求に証明書の拇印を含めます
  2. トークン バインド: 承認サーバーは、発行されたトークンに証明書の SHA-256 拇印 (cnf) を含むx5t#S256 (確認) 要求を埋め込みます。
  3. API 呼び出し: クライアントは、ダウンストリーム API を呼び出すときにバインドされたトークンと証明書の両方を提示します
  4. 検証: API は、提示された証明書がトークンの cnf 要求の証明書参照と一致することを検証します
sequenceDiagram
    participant Client
    participant EntraID as Microsoft Entra ID
    participant API

    Client->>EntraID: Token request with certificate thumbprint
    EntraID->>Client: Token with cnf claim (bound to certificate)
    Client->>API: MTLS_POP token + Client certificate
    API->>API: Validate token and certificate binding
    API->>Client: Protected resource

セキュリティ上の利点を確認する

トークン バインディングには、アプリケーションをセキュリティで保護するための次の利点があります。

  • トークン盗難防止: 盗まれたトークンは、対応する証明書がないと役に立ちません
  • リプレイ攻撃防止: トークンは異なるクライアントからリプレイできません
  • 拡張認証: "持っているもの" (証明書) と従来の OAuth2 フローを組み合わせたもの
  • ゼロ トラスト アーキテクチャ: 資格情報を特定のデバイスにバインドすることで、zero trust原則に合わせて調整します

トークン バインドを構成する

mTLS PoP トークン バインディングを有効にするために、クライアント アプリケーションと API サーバーの両方を設定します。

クライアント アプリケーションを構成する

トークン バインディング用にクライアント アプリケーションを構成するには、次の手順を実行します。

1. Microsoft Entra ID設定を構成する

appsettings.jsonで、証明書を含むMicrosoft Entra設定を構成します。

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "ClientCredentials": [
      {
        "SourceType": "StoreWithDistinguishedName",
        "CertificateStorePath": "CurrentUser/My",
        "CertificateDistinguishedName": "CN=YourCertificate"
      }
    ],
    "SendX5c": true
  }
}

2.トークン バインディングを使用してダウンストリーム API を構成する

MTLS_POP プロトコル スキームを使用してダウンストリーム API セクションを構成します。

{
  "DownstreamApi": {
    "BaseUrl": "https://api.contoso.com/",
    "RelativePath": "api/data",
    "ProtocolScheme": "MTLS_POP",
    "RequestAppToken": true,
    "Scopes": [ "api://your-api-scope/.default" ]
  }
}

重要な構成プロパティ:

  • ProtocolScheme: トークン バインディングを有効にするには、 "MTLS_POP" に設定する必要があります
  • RequestAppToken: true する必要があります (トークン バインドは現在、アプリケーション トークンのみをサポートしています)
  • Scopes: ダウンストリーム API 呼び出しに必要な API スコープ

3. サービスの登録

ダウンストリーム API サービスをアプリケーションのスタートアップ コードに登録します。 次の例は、コンソール アプリと ASP.NET Coreの両方の方法を示しています。

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Option 1: Using TokenAcquirerFactory (for console apps, background services)
var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();

tokenAcquirerFactory.Services.AddDownstreamApi(
    "DownstreamApi",
    tokenAcquirerFactory.Configuration.GetSection("DownstreamApi"));

var serviceProvider = tokenAcquirerFactory.Build();

// Option 2: Using ASP.NET Core DI (for web apps, web APIs)
builder.Services.AddAuthentication()
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddDownstreamApi(
    "DownstreamApi",
    builder.Configuration.GetSection("DownstreamApi"));

API サーバーを構成する

ダウンストリーム API は、トークンと証明書バインドの両方を検証する必要があります。 完全な例を次に示します。

1. 認証ハンドラーを登録する

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Add standard JWT Bearer authentication
builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration);

// Add custom MTLS_POP authentication handler
builder.Services.AddAuthentication()
    .AddScheme<AuthenticationSchemeOptions, MtlsPopAuthenticationHandler>(
        "MTLS_POP",
        options => { });

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();

2. mTLS PoP 認証ハンドラーを実装する

using System.Security.Claims;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.Encodings.Web;
using System.Text.Json;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

public class MtlsPopAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public const string ProtocolScheme = "MTLS_POP";

    public MtlsPopAuthenticationHandler(
        IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder)
        : base(options, logger, encoder)
    {
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // 1. Extract the MTLS_POP authorization header
        var authHeader = Request.Headers.Authorization.FirstOrDefault();
        if (string.IsNullOrEmpty(authHeader) ||
            !authHeader.StartsWith($"{ProtocolScheme} ", StringComparison.OrdinalIgnoreCase))
        {
            return AuthenticateResult.NoResult();
        }

        var authToken = authHeader.Substring($"{ProtocolScheme} ".Length).Trim();

        try
        {
            // 2. Parse the JWT token
            var handler = new JsonWebTokenHandler();
            var token = handler.ReadJsonWebToken(authToken);

            // 3. Extract the 'cnf' claim
            var cnfClaim = token.Claims.FirstOrDefault(c => c.Type == "cnf");
            if (cnfClaim == null)
            {
                return AuthenticateResult.Fail("Missing 'cnf' claim in MTLS_POP token");
            }

            // 4. Extract certificate thumbprint from cnf claim
            var cnfJson = JsonDocument.Parse(cnfClaim.Value);
            if (!cnfJson.RootElement.TryGetProperty("x5t#S256", out var x5tS256Element))
            {
                return AuthenticateResult.Fail("Missing 'x5t#S256' in cnf claim");
            }

            var expectedThumbprint = x5tS256Element.GetString();

            // 5. Get client certificate from TLS connection
            var clientCert = Context.Connection.ClientCertificate;
            if (clientCert != null)
            {
                var actualThumbprint = GetCertificateThumbprint(clientCert);

                // 6. Validate certificate binding
                if (!string.Equals(actualThumbprint, expectedThumbprint,
                    StringComparison.OrdinalIgnoreCase))
                {
                    return AuthenticateResult.Fail(
                        "Certificate thumbprint mismatch with cnf claim");
                }
            }

            // 7. Create claims principal
            var claims = token.Claims.Select(c => new Claim(c.Type, c.Value)).ToList();
            var identity = new ClaimsIdentity(claims, ProtocolScheme);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, ProtocolScheme);

            return AuthenticateResult.Success(ticket);
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error validating mTLS PoP token");
            return AuthenticateResult.Fail($"Validation error: {ex.Message}");
        }
    }

    private static string GetCertificateThumbprint(X509Certificate2 certificate)
    {
        using var sha256 = SHA256.Create();
        var hash = sha256.ComputeHash(certificate.RawData);
        return Base64UrlEncoder.Encode(hash);
    }
}

アプリケーションでトークン バインディングを使用する

次の例では、mTLS PoP トークン バインドをさまざまなアプリケーションの種類に統合する方法を示します。

コンソールまたはデーモン アプリケーションから API を呼び出す

次の例は、mTLS PoP トークン バインディングを使用してダウンストリーム API を呼び出すコンソールまたはデーモン アプリケーションを示しています。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;

public class Program
{
    public static async Task Main(string[] args)
    {
        // Create and configure token acquirer
        var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();

        tokenAcquirerFactory.Services.AddDownstreamApi(
            "SecureApi",
            tokenAcquirerFactory.Configuration.GetSection("SecureApi"));

        var serviceProvider = tokenAcquirerFactory.Build();

        // Get IDownstreamApi instance
        var downstreamApi = serviceProvider.GetRequiredService<IDownstreamApi>();

        // Call API with mTLS PoP token
        var response = await downstreamApi.GetForAppAsync<ApiResponse>("SecureApi");

        Console.WriteLine($"Result: {response?.Data}");
    }
}

public class ApiResponse
{
    public string? Data { get; set; }
}

ASP.NET Core Web アプリケーションから API を呼び出す

次の例は、mTLS PoP トークン バインディングを使用してダウンストリーム API を呼び出すコントローラーを示しています。

using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Abstractions;

[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
    private readonly IDownstreamApi _downstreamApi;
    private readonly ILogger<DataController> _logger;

    public DataController(
        IDownstreamApi downstreamApi,
        ILogger<DataController> logger)
    {
        _downstreamApi = downstreamApi;
        _logger = logger;
    }

    [HttpGet]
    public async Task<IActionResult> GetSecureData()
    {
        try
        {
            // Call downstream API with mTLS PoP token binding
            var data = await _downstreamApi.GetForAppAsync<SecureData>(
                "SecureApi");

            return Ok(data);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to retrieve secure data");
            return StatusCode(500, "Failed to retrieve data");
        }
    }
}

public class SecureData
{
    public string? Id { get; set; }
    public string? Value { get; set; }
}

プログラムで DownstreamApiOptions を構成する

次の例では、構成ファイルの代わりに mTLS PoP オプションをコードで直接設定します。

using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;

public class SecureApiService
{
    private readonly IDownstreamApi _downstreamApi;

    public SecureApiService(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<T?> CallSecureApiAsync<T>(string endpoint) where T : class
    {
        return await _downstreamApi.GetForAppAsync<T>(
            serviceName: null,
            downstreamApiOptionsOverride: options =>
            {
                options.BaseUrl = "https://api.secure.com";
                options.RelativePath = endpoint;
                options.ProtocolScheme = "MTLS_POP";
                options.RequestAppToken = true;
                options.Scopes = new[] { "api://secure-api/.default" };
            });
    }
}

トークン バインディングで MicrosoftIdentityMessageHandler を使用する

MicrosoftIdentityMessageHandler では、 AddMicrosoftIdentityMessageHandler 拡張メソッドを介した mTLS PoP トークン バインドがサポートされています。 ProtocolScheme"MTLS_POP" に設定されている場合、ハンドラーはバインドされたトークンを自動的に取得し、mTLS で構成された HTTP クライアントを介して要求を送信します。

インライン オプションを構成する

次の例では、インライン mTLS PoP 構成で HTTP クライアントを登録し、サービスでの使用状況を示します。

// Program.cs
services.AddHttpClient("MtlsPopClient", client =>
{
    client.BaseAddress = new Uri("https://api.contoso.com");
})
.AddMicrosoftIdentityMessageHandler(options =>
{
    options.Scopes.Add("api://contoso/.default");
    options.ProtocolScheme = "MTLS_POP";
    options.RequestAppToken = true;
});

// Usage in a service
public class SecureApiService
{
    private readonly HttpClient _httpClient;

    public SecureApiService(IHttpClientFactory factory)
    {
        _httpClient = factory.CreateClient("MtlsPopClient");
    }

    public async Task<string> GetSecureDataAsync()
    {
        // Authentication and mTLS certificate binding are automatic
        var response = await _httpClient.GetAsync("/api/secure-data");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

appsettings.json から構成を読み込む

構成ファイルからトークン バインド設定を読み込むこともできます。

appsettings.json:

{
  "DownstreamApis": {
    "SecureApi": {
      "Scopes": ["api://secure-api/.default"],
      "ProtocolScheme": "MTLS_POP",
      "RequestAppToken": true
    }
  }
}

Program.cs: 次のコードは、構成セクションを使用して HTTP クライアントを登録します。

services.AddHttpClient("SecureApiClient", client =>
{
    client.BaseAddress = new Uri("https://secure-api.example.com");
})
.AddMicrosoftIdentityMessageHandler(
    configuration.GetSection("DownstreamApis:SecureApi"),
    "SecureApi");

要求ごとのトークン バインドを適用する

要求ごとのオプションは、一部の要求でトークン バインドが必要であり、それ以外の要求では使用されない場合に使用します。

services.AddHttpClient("FlexibleClient")
    .AddMicrosoftIdentityMessageHandler();

// In a service:
public async Task<string> CallWithTokenBindingAsync()
{
    var request = new HttpRequestMessage(HttpMethod.Get, "https://api.contoso.com/secure")
        .WithAuthenticationOptions(options =>
        {
            options.Scopes.Add("api://contoso/.default");
            options.ProtocolScheme = "MTLS_POP";
            options.RequestAppToken = true;
        });

    var response = await _httpClient.SendAsync(request);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync();
}

MicrosoftIdentityMessageHandlerの詳細については、カスタム API のドキュメントを参照してください

承認ヘッダー プロバイダーを使用してカスタム HttpClient を作成する

HTTP 要求をより詳細に制御する必要があるシナリオでは、このアプローチを使用します。 次の例では、バインドされた承認ヘッダーを取得し、mTLS で構成された HTTP クライアントを作成します。

using Microsoft.Identity.Abstractions;
using System.Net.Http.Headers;

public class CustomApiClient
{
    private readonly IAuthorizationHeaderProvider _authProvider;
    private readonly IHttpClientFactory _httpClientFactory;

    public CustomApiClient(
        IAuthorizationHeaderProvider authProvider,
        IHttpClientFactory httpClientFactory)
    {
        _authProvider = authProvider;
        _httpClientFactory = httpClientFactory;
    }

    public async Task<string> CallApiWithCustomLogicAsync()
    {
        // Create downstream API options for mTLS PoP
        var apiOptions = new DownstreamApiOptions
        {
            BaseUrl = "https://api.contoso.com",
            ProtocolScheme = "MTLS_POP",
            RequestAppToken = true,
            Scopes = new[] { "api://contoso/.default" }
        };

        // Get authorization header with binding certificate info
        var authResult = await (_authProvider as IBoundAuthorizationHeaderProvider)
            ?.CreateBoundAuthorizationHeaderAsync(apiOptions)!;

        if (authResult.IsSuccess)
        {
            // Create HTTP client with certificate binding
            var httpClient = authResult.Value.BindingCertificate != null
                ? CreateMtlsHttpClient(authResult.Value.BindingCertificate)
                : _httpClientFactory.CreateClient();

            // Set authorization header
            httpClient.DefaultRequestHeaders.Authorization =
                AuthenticationHeaderValue.Parse(authResult.Value.AuthorizationHeaderValue);

            // Make API call
            var response = await httpClient.GetAsync(
                $"{apiOptions.BaseUrl}/api/endpoint");

            return await response.Content.ReadAsStringAsync();
        }

        throw new InvalidOperationException("Failed to acquire token");
    }

    private HttpClient CreateMtlsHttpClient(X509Certificate2 certificate)
    {
        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(certificate);
        return new HttpClient(handler);
    }
}

トークン構造を調べる

次の例は、標準トークンとバインドトークンの違いを示しています。

標準の OAuth2 トークンを比較する

標準の OAuth2 トークンには、証明書のバインド情報が含まれていません。

{
  "aud": "api://your-api",
  "iss": "https://login.microsoftonline.com/tenant-id/",
  "iat": 1234567890,
  "exp": 1234571490,
  "appid": "client-id",
  "tid": "tenant-id"
}

バインドを使用して mTLS PoP トークンを確認する

mTLS PoP トークンには、トークンを特定の証明書にバインドする cnf 要求が含まれます。

{
  "aud": "api://your-api",
  "iss": "https://login.microsoftonline.com/tenant-id/",
  "iat": 1234567890,
  "exp": 1234571490,
  "appid": "client-id",
  "tid": "tenant-id",
  "cnf": {
    "x5t#S256": "buc7x2HxS_hPnVJb9J5mwPr6jCw8Y_2LHDz-gp_-6KM"
  }
}

cnf (確認) 要求には、Base64Url でエンコードされた証明書の SHA-256 拇印が含まれています。

現在の制限事項を理解する

mTLS PoP トークン バインドを実装する前に、次の制約を確認してください。

アプリケーション トークンのみをサポートする

トークン バインディングでは現在、 アプリケーション (アプリ専用) トークンのみがサポートされています。 委任された (ユーザー) トークンはサポートされていません。

プロトコル スキームを設定する

トークン バインディングを有効にするには、 ProtocolScheme プロパティを明示的に "MTLS_POP" に設定する必要があります。 設定されていない場合は、標準ベアラー認証が使用されます。

証明書の要件を満たす

  • 証明書は、ClientCredentialsSendX5cに設定してtrueで構成する必要があります。
  • トークンの取得時に証明書にアクセスできる必要があります

一般的な問題のトラブルシューティング

トークン バインディングの問題を診断して解決するには、次のガイダンスを使用します。

一般的な問題を解決

1. "トークンに 'cnf' 要求がありません"

原因: トークン バインディングが正しく構成されていないか、トークンが標準ベアラー トークンです。

解決策: ProtocolScheme"MTLS_POP" に設定され、 RequestAppTokentrueされていることを確認します。

{
  "DownstreamApi": {
    "ProtocolScheme": "MTLS_POP",  // ensure this is set
    "RequestAppToken": true
  }
}

2. "証明書の拇印の不一致"

原因: API に提示される証明書が、トークンの取得に使用される証明書と一致しません。

解決策:

  • トークンの取得と API 呼び出しの両方に同じ証明書が使用されていることを確認する
  • で証明書の読み込み構成を確認する ClientCredentials
  • 証明書の有効期限が切れていないか、更新されていないことを確認する

3. "トークン バインドに必要な証明書が見つかりません"

Cause: Microsoft Entra設定で証明書が構成されていない。

解決策: ClientCredentials 構成に証明書を追加し、 SendX5ctrue に設定します。

{
  "AzureAd": {
    "ClientCredentials": [
      {
        "SourceType": "StoreWithDistinguishedName",
        "CertificateStorePath": "CurrentUser/My",
        "CertificateDistinguishedName": "CN=YourCertificate"
      }
    ],
    "SendX5c": true  // required for token binding
  }
}

4. "トークン バインディングには、有効なアプリ トークンの取得が必要です"

原因: RequestAppTokentrue に設定されていません。

解決策: RequestAppToken をオプションで true に設定します。

var options = new DownstreamApiOptions
{
    ProtocolScheme = "MTLS_POP",
    RequestAppToken = true,  // must be true
};

デバッグトークンのバインディング

トークン バインディングの問題を調査するには、次の手法を使用します。

詳細なログ記録を有効にする

Microsoft.Identity.Webのデバッグレベルのログを有効にするために、次の構成を追加します。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.Identity.Web": "Debug"
    }
  }
}

トークン要求を検査する

トークン内のすべての要求を一覧表示し、 cnf 要求を確認するには、次のコードを使用します。

var handler = new JsonWebTokenHandler();
var token = handler.ReadJsonWebToken(tokenString);

foreach (var claim in token.Claims)
{
    Console.WriteLine($"{claim.Type}: {claim.Value}");
}

// Look for 'cnf' claim with x5t#S256
var cnfClaim = token.Claims.FirstOrDefault(c => c.Type == "cnf");

証明書の拇印を確認する

次のコードを使用して、比較のために証明書の SHA-256 拇印を計算して表示します。

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.IdentityModel.Tokens;

var cert = new X509Certificate2("path/to/cert.pfx", "password");
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(cert.RawData);
var thumbprint = Base64UrlEncoder.Encode(hash);
Console.WriteLine($"Certificate thumbprint: {thumbprint}");

セキュリティ ガイドラインに従う

トークン バインディングを実装する場合は、次のセキュリティ プラクティスを適用します。

証明書を安全に管理する

  • 安全にストアする: Azure Key Vaultまたはセキュリティで保護された証明書ストアを使用する
  • 定期的にローテーションする: 証明書のローテーション 手順を実装する
  • 有効期限の監視: 証明書の有効期限のアラートを設定する
  • アクセスの制限: 証明書の秘密キーにアクセスできるユーザーを制限する

セキュリティで保護されたネットワーク接続

  • TLS 1.2 以降が必要: すべての接続で最新の TLS バージョンが使用されていることを確認する
  • 証明書の検証: サーバーに適切な証明書検証を実装する
  • 強力な暗号を使用する: セキュリティで保護された暗号スイートを構成する

トークンを安全に処理する

  • 有効期間が短い: 有効期間の短いトークンを使用する (推奨: 1 時間)
  • 適切なストレージ: トークンのログ記録や公開を行わない
  • 十分に検証する: すべての要求、有効期限、バインドを確認する

ベスト プラクティスに従う

mTLS PoP トークン バインディングをデプロイするときは、次の推奨事項に留意してください。

  1. 常に HTTPS を使用する: mTLS PoP には安全なトランスポートが必要
  2. TPM など、ハードウェアに秘密キーマテリアルを格納する証明書を使用する: 保護を強化するためにソフトウェア セキュリティ経由でハードウェアを使用する
  3. 適切なエラー処理を実装する: 証明書とトークンのエラーを適切に処理する
  4. 証明書の有効期限を監視する: 証明書の更新を自動化する
  5. 環境ごとに個別の証明書を使用する: 開発、ステージング、運用の証明書
  6. ログ セキュリティ イベント: トークン バインドエラーと証明書の不一致を追跡する
  7. 証明書のローテーションをテストする: アプリケーションが証明書の更新を処理することを確認する
  8. 構成を文書化する: 証明書の要件に関する明確なドキュメントを保持する

サンプル コードを調べる

mTLS PoP トークン バインドを示す完全な作業サンプルは、リポジトリで入手できます。

これらのサンプルは、次の例を示しています。

  • 完全なクライアントとサーバーの構成
  • 証明書バインドを使用したトークンの取得
  • カスタム認証ハンドラーの実装
  • 証明書の検証と拇印の検証