C#でAzure AD(Entra ID)認証

自分自身の備忘録的な意味で記事に残します。で、もしかしたらどなたかの解決の糸口になるかな?

Azure AD(Microsoft Entra ID)へ認証用のトークンを発行しに行く際に「ユーザ」「パスワード」を利用して取得する方法。

もう古い技術?かと思いますが、環境によってはシステム導入先がこの認証方法でないと対応できない!とかあるかと思います。なので完全に新規プロジェクトであれば以下の認証方法は行わないかと思いますが。

Sponsored Link

結論としてコードを書いちゃいます。MSAL.NET

using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security;
using System.Threading.Tasks;
using Microsoft.Identity.Client;

public class AzureAuthClient
{
    // 1. 設定情報(本来は構成ファイルなどから読み込む)
    private const string ClientId = "アプリのクライアントID";
    private const string TenantId = "テナントID(または'common')";
    private const string ApiBaseUrl = "https://api.example.com/"; // 対象となるURL
    
    // スコープ:ここで「user_impersonation」を指定
    private readonly string[] _scopes = { $"api://{ClientId}/user_impersonation" };

    private readonly IPublicClientApplication _app; // これを使いまわします!
    private readonly string _username = "someone@example.com"; // 認証用ユーザ
    private readonly SecureString _password; // 認証用ユーザのパスワード

    public AzureAuthClient(string password)
    {
        // アプリケーションの初期化。その後この「_app」を使いまわします。
        _app = PublicClientApplicationBuilder.Create(ClientId)
            .WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
            .Build();

        // パスワードをセキュアに保持
        _password = new SecureString();
        foreach (var c in password) _password.AppendChar(c);
    }

    /// <summary>
    /// キャッシュを考慮して有効なアクセストークンを取得する
    /// </summary>
    private async Task<string> GetAccessTokenAsync()
    {
        var accounts = await _app.GetAccountsAsync();   // すべてのIAccountオブジェクトを取得
        AuthenticationResult result;

        try
        {
            // 【重要】まずはキャッシュ(AcquireTokenSilent)を確認
            // これにより、Azureへの無駄なリクエストを防いで「リクエスト多すぎ」エラーを回避
            result = await _app.AcquireTokenSilent(_scopes, accounts.FirstOrDefault())
                .ExecuteAsync();
            Console.WriteLine("--- キャッシュからトークンを取得しました ---");
        }
        catch (MsalUiRequiredException)
        {
            // キャッシュにない、または期限切れの場合は、ユーザーID/パスワードで新規取得
            result = await _app.AcquireTokenByUsernamePassword(_scopes, _username, _password)
                .ExecuteAsync();
            Console.WriteLine("--- Azure ADから新規にトークンを取得しました ---");
        }

        return result.AccessToken;
    }

    /// <summary>
    /// トークンを付与してAPIを呼び出す
    /// </summary>
    public async Task CallMyApiAsync()
    {
        // トークンの取得
        string token = await GetAccessTokenAsync();

        // HttpClientの生成
        using (var client = new HttpClient())
        {
            client.BaseAddress = new Uri(ApiBaseUrl);

            // 【重要】AuthorizationヘッダーにBearerトークンをセット
            client.DefaultRequestHeaders.Authorization = 
                new AuthenticationHeaderValue("Bearer", token);

            // API呼び出し(例:社員情報取得)
            Console.WriteLine("APIを呼び出しています...");
            var response = await client.GetAsync("api/v1/employees");

            if (response.IsSuccessStatusCode)
            {
                string json = await response.Content.ReadAsStringAsync();
                Console.WriteLine("API呼び出し成功!");
                Console.WriteLine(json);
            }
            else
            {
                // ここで401や403が出たら、Azure側のuser_impersonation設定を疑う!
                Console.WriteLine($"API呼び出し失敗:{response.StatusCode}");
            }
        }
    }
}

内容についてはソース内にコメントを追加しておきました。

Sponsored Link

トークンはキャッシュから。

ここまで来るには多少の紆余曲折がありまして。。。

もちろん今になれば調べ方次第ですぐにこのソース状態に持って行けたんでしょうけど「Azure Portalの設定画面をエンドユーザの情シスの方でないと見せられない」みたいな状況で、手探り状態・・・

最初は「キャッシュ」から取得するという概念がなかったため、その都度「Azureサーバ」にトークンを取得しに行く形になっていて。

そうなると、テストを何回も重ねると「AADSTS50196」というエラーコードが・・・

スロットリングエラー」というやつですね。

で、「AcquireTokenSilent」というキーワードから最終的に上のソースになりました。

あと「IPublicClientApplication」を「都度、初期化」するようなことがないように。恥ずかしながらコードを書いた際に毎回初期化されて「トークンがない」みたいなバカみたいなエラーになっていたことがあったので。

ご参考までに。

タイトルとURLをコピーしました