Azure AD (Microsoft Entra ID) アプリケーションのクライアント資格情報について
はじめに
Azure AD (Microsoft Entra ID) アプリケーションの認証や証明書の取り扱いについて、混乱しやすいポイントが多いため、整理してまとめます。本記事では、証明書の種類や作成方法、認証フローごとの実装例について解説します。
機密クライアント アプリケーションの認証方法
Azure AD (Microsoft Entra ID) アプリケーションには大きくわけて 2 種類あります。
種類 | 実行場所 | 例 |
---|---|---|
機密クライアント アプリケーション | サーバー | Webアプリ、Web APIアプリ、デーモンアプリ |
パブリック クライアント アプリケーション | クライアント | デスクトップアプリ、モバイルアプリ、SPAアプリ |
機密クライアント アプリケーション で認証フローを実施する場合は、クライアント資格情報を提供する必要があります。資格情報としては クライアント シークレット または クライアント証明書 を使用できます。クライアント シークレット は認証フローが簡単なためよく使われますが、リクエストに クライアント シークレット を含めて送信する必要があるため、セキュリティ上の安全性は低くなります。一方、クライアント証明書 は、証明書そのものではなく、証明書を使って署名された クライアント アサーション を送信するため、セキュリティ上の安全性が高いです。複数のアプリケーションで同じ証明書を使うことができるため、管理上のメリットが発生する場合もあります。いずれの場合も有効期限には注意が必要です。
種類 | 難易度 | 安全性 | アプリ間の共有 |
---|---|---|---|
クライアント シークレット | 低 | 低 | できない |
クライアント証明書 | 高 | 高 | できる |
SharePoint のアプリ専用のアクセス許可は クライアント シークレット をサポートしていません。そのため クライアント証明書 を使用しなければならない場合もあります。
証明書の種類
証明書にもさまざまな種類があり、非常にややこしいです。簡単に説明すると以下のようになります。拡張子は慣習なので cer や key はそれぞれ der や pem の場合があります。また、証明書署名リクエストを示す csr もありますが、認証フローでは使わないため今回は除外します。それぞれの形式は OpenSSL を使って変換できます。
OS | 拡張子 | 鍵の種類 | データ形式 |
---|---|---|---|
Windows | cer | 公開鍵 | バイナリ形式 (DER) |
pfx | 秘密鍵 | バイナリ形式 (PKCS #12) | |
Linux | crt | 公開鍵 | テキスト形式 (PEM) |
key | 秘密鍵 | テキスト形式 (PEM) |
認証フローとしてはどちらの形式でも実施できますが、MSAL (Microsoft Authentication Library) を使用する場合、実装によってはどちらかの形式しか対応していないことがあります。たとえば MSAL.NET の場合は X509Certificate2 クラスを使って証明書をインポートできますが、.NET 5 以前のバージョンでは PEM 形式には対応していません。
証明書については、一般的な SSL 通信では認証局 (CA) から発行された証明書を使用する必要がありますが、Azure AD (Microsoft Entra ID) アプリケーションの場合は自己署名証明書でも問題ありません。ただし、Microsoft としてはセキュリティを強化するため証明機関から証明書を購入することを推奨しています。
自己署名証明書は既定では信頼されないため、管理が困難になる可能性があります。また、強力でない可能性のある旧式のハッシュや暗号スイートが使用される場合もあります。セキュリティを強化するには、よく知られている証明機関によって署名された証明書を購入してください。
以下は自己署名証明書を使用する前提で説明します。
実行手順 (Windowsの場合)
証明書の作成
Windows の場合は Windows PowerShell を使って自己署名証明書を作成できます。作成した証明書は証明書ストアに格納されます。
$cert = New-SelfSignedCertificate -DnsName "{{domain-name}}" -NotBefore "{{start-date}}" -NotAfter "{{end-date}}" -CertStoreLocation "cert:\CurrentUser\My"
作成した証明書の公開鍵 (cer ファイル) をエクスポートします。
Export-Certificate -Cert $cert -FilePath "{{file-name}}.cer"
作成した証明書の秘密鍵 (pfx ファイル) をエクスポートします。秘密鍵のエクスポートにはパスワードが必要な点に注意してください。これは秘密鍵を取り出すときに使用されるもので、実際の秘密鍵の内容とは関係ありません。
Export-PfxCertificate -Cert $cert -FilePath "{{file-name}}.pfx" -Password (ConvertTo-SecureString -String "{{password}}" -AsPlainText -Force)
証明書のアップロード
作成した公開鍵 (cer ファイル) を Azure AD (Microsoft Entra ID) アプリケーションにアップロードします。
コードの記述
クライアント資格情報フロー (Client Credentials Flow) を使用して実際にアクセスできることを確認します。
はじめに MSAL.NET のパッケージを追加します。
dotnet add package Microsoft.Identity.Client
コード例は以下のとおりです。
using Microsoft.Identity.Client;
using System.Security.Cryptography.X509Certificates;
var tenantId = "{{tenant-id}}";
var clientId = "{{client-id}}";
var pfxPath = "{{file-name}}.pfx";
var pfxPassword = "{{password}}";
var msalApp = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.WithCertificate(new X509Certificate2(pfxPath, pfxPassword))
.Build();
var msalResult = await msalApp
.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
.ExecuteAsync();
Console.WriteLine(msalResult.AccessToken);
実行手順 (Linuxの場合)
証明書の作成
Linux の場合は OpenSSL を使って自己署名証明書を作成できます。
秘密鍵 (key ファイル) を作成します。
openssl genrsa -out "{{file-name}}.key" 2048
公開鍵 (crt ファイル) を作成します。
openssl req -x509 -nodes -new -keyout "{{file-name}}.key" -out "{{file-name}}.crt" -days 365 -subj "/CN={{domain-name}}"
証明書のアップロード
作成した公開鍵 (crt ファイル) を Azure AD (Microsoft Entra ID) アプリケーションにアップロードします。
コードの記述
クライアント資格情報フロー (Client Credentials Flow) を使用して実際にアクセスできることを確認します。
はじめに MSAL.NET のパッケージを追加します。
dotnet add package Microsoft.Identity.Client
コード例は以下のとおりです。
using Microsoft.Identity.Client;
using System.Security.Cryptography.X509Certificates;
var tenantId = "{{tenant-id}}";
var clientId = "{{client-id}}";
var crtPath = "{{file-name}}.crt";
var keyPath = "{{file-name}}.key";
var msalApp = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.WithCertificate(X509Certificate2.CreateFromPemFile(crtPath, keyPath))
.Build();
var msalResult = await msalApp
.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
.ExecuteAsync();
Console.WriteLine(msalResult.AccessToken);
実行手順 (低レイヤー)
上記の CreateFromPemFile メソッドは .NET 5.0 以上をサポートしているため、.NET Standard の場合は使用できません。その場合は クライアント証明書 ではなく クライアント アサーション を作成する必要があります。
クライアント アサーション の作成方法については以下のドキュメントを参照してください。
実際に key ファイルを使って認証するコード例は以下のとおりです。拇印は Azure AD (Microsoft Entra ID) アプリケーションに公開鍵 (crt ファイル) を登録したときに取得できます。
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Identity.Client;
var tenantId = "{{tenant-id}}";
var clientId = "{{client-id}}";
var keyPath = "{{file-name}}.key";
var thumbprint = "{{thumbprint}}";
// 秘密鍵のインポート
var keyFile = File.ReadAllText(keyPath)
.Replace("-----BEGIN PRIVATE KEY-----", "")
.Replace("-----END PRIVATE KEY-----", "")
.Replace(Environment.NewLine, "");
var rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(keyFile), out var _);
// ヘッダーの作成
var header = new Dictionary<string, string>()
{
{ "alg", "RS256" },
{ "typ", "JWT" },
{ "x5t", Base64UrlEncode(Convert.FromHexString(thumbprint)) },
};
// クレームの作成
var claims = new Dictionary<string, object>()
{
{ "aud", $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token" },
{ "exp", DateTimeOffset.UtcNow.AddMinutes(10).ToUnixTimeSeconds() },
{ "iss", clientId },
{ "jti", Guid.NewGuid().ToString() },
{ "nbf", DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
{ "sub", clientId }
};
// アサーションの作成
var headerBytes = JsonSerializer.SerializeToUtf8Bytes(header);
var claimsBytes = JsonSerializer.SerializeToUtf8Bytes(claims);
var token = Base64UrlEncode(headerBytes) + "." + Base64UrlEncode(claimsBytes);
var signature = Base64UrlEncode(rsa.SignData(Encoding.UTF8.GetBytes(token), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
var assertion = string.Concat(token, ".", signature);
var msalApp = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.WithClientAssertion(assertion)
.Build();
var msalResult = await msalApp
.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
.ExecuteAsync();
Console.WriteLine(msalResult.AccessToken);
static string Base64UrlEncode(byte[] bytes)
{
const char Base64PadCharacter = '=';
const char Base64Character62 = '+';
const char Base64Character63 = '/';
const char Base64UrlCharacter62 = '-';
const char Base64UrlCharacter63 = '_';
return Convert.ToBase64String(bytes)
.Split(Base64PadCharacter)[0]
.Replace(Base64Character62, Base64UrlCharacter62)
.Replace(Base64Character63, Base64UrlCharacter63);
}
おわりに
.NET Framework では ImportPkcs8PrivateKey メソッドが利用できないため、証明書の取り扱いがさらに複雑になります。どうしても必要な場合は BouncyCastle などの外部ライブラリを利用する方法もありますが、基本的には Windows であれば pfx ファイルを利用することをおすすめします。用途や環境に応じて、適切な証明書形式と認証方式を選択してください。
Discussion