Note
この機能は現在プライベート プレビュー段階であるため、すべてのクライアントが mTLS PoP 証明書を取得できるわけではありません。
証明書トークン バインド (mTLS PoP - 相互 TLS 所有証明とも呼ばれます) は、アクセス トークンを特定の X.509 証明書に暗号でバインドする高度なセキュリティ機能です。 RFC 8705 では、このバインディングについて説明します。 このバインディングにより、トークンが傍受された場合でも、攻撃者は対応する秘密キーを所有せずにトークンを使用できなくなります。
トークン バインドのしくみを理解する
次の手順では、取得から検証までのトークン バインド フローについて説明します。
- トークン取得: トークン バインドが有効になっているアクセス トークンを要求する場合、Microsoft Identity Web はトークン要求に証明書の拇印を含めます
-
トークン バインド: 承認サーバーは、発行されたトークンに証明書の SHA-256 拇印 (
cnf) を含むx5t#S256(確認) 要求を埋め込みます。 - API 呼び出し: クライアントは、ダウンストリーム API を呼び出すときにバインドされたトークンと証明書の両方を提示します
-
検証: 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" に設定する必要があります。 設定されていない場合は、標準ベアラー認証が使用されます。
証明書の要件を満たす
- 証明書は、
ClientCredentialsをSendX5cに設定してtrueで構成する必要があります。 - トークンの取得時に証明書にアクセスできる必要があります
一般的な問題のトラブルシューティング
トークン バインディングの問題を診断して解決するには、次のガイダンスを使用します。
一般的な問題を解決
1. "トークンに 'cnf' 要求がありません"
原因: トークン バインディングが正しく構成されていないか、トークンが標準ベアラー トークンです。
解決策: ProtocolScheme が "MTLS_POP" に設定され、 RequestAppToken が trueされていることを確認します。
{
"DownstreamApi": {
"ProtocolScheme": "MTLS_POP", // ensure this is set
"RequestAppToken": true
}
}
2. "証明書の拇印の不一致"
原因: API に提示される証明書が、トークンの取得に使用される証明書と一致しません。
解決策:
- トークンの取得と API 呼び出しの両方に同じ証明書が使用されていることを確認する
- で証明書の読み込み構成を確認する
ClientCredentials - 証明書の有効期限が切れていないか、更新されていないことを確認する
3. "トークン バインドに必要な証明書が見つかりません"
Cause: Microsoft Entra設定で証明書が構成されていない。
解決策: ClientCredentials 構成に証明書を追加し、 SendX5c を true に設定します。
{
"AzureAd": {
"ClientCredentials": [
{
"SourceType": "StoreWithDistinguishedName",
"CertificateStorePath": "CurrentUser/My",
"CertificateDistinguishedName": "CN=YourCertificate"
}
],
"SendX5c": true // required for token binding
}
}
4. "トークン バインディングには、有効なアプリ トークンの取得が必要です"
原因: RequestAppToken が true に設定されていません。
解決策: 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 トークン バインディングをデプロイするときは、次の推奨事項に留意してください。
- 常に HTTPS を使用する: mTLS PoP には安全なトランスポートが必要
- TPM など、ハードウェアに秘密キーマテリアルを格納する証明書を使用する: 保護を強化するためにソフトウェア セキュリティ経由でハードウェアを使用する
- 適切なエラー処理を実装する: 証明書とトークンのエラーを適切に処理する
- 証明書の有効期限を監視する: 証明書の更新を自動化する
- 環境ごとに個別の証明書を使用する: 開発、ステージング、運用の証明書
- ログ セキュリティ イベント: トークン バインドエラーと証明書の不一致を追跡する
- 証明書のローテーションをテストする: アプリケーションが証明書の更新を処理することを確認する
- 構成を文書化する: 証明書の要件に関する明確なドキュメントを保持する
関連するコンテンツ
- Microsoft Id Web ドキュメント
- ダウンストリーム API の呼び出しの概要
- カスタム API のドキュメント
- Microsoft Entra 証明書資格情報
- OAuth 2.0 Mutual-TLS クライアント認証
サンプル コードを調べる
mTLS PoP トークン バインドを示す完全な作業サンプルは、リポジトリで入手できます。
-
クライアント アプリケーション:
tests/DevApps/MtlsPop/MtlsPopClient -
Web API Server:
tests/DevApps/MtlsPop/MtlsPopWebApi
これらのサンプルは、次の例を示しています。
- 完全なクライアントとサーバーの構成
- 証明書バインドを使用したトークンの取得
- カスタム認証ハンドラーの実装
- 証明書の検証と拇印の検証