Azure Identity SDKのDefaultAzureCredentialでローカルデバッグ時に詰まった話

この記事はなむゆの個人ブログにもマルチポストしてあります。

はじめに

Azure DevOpsとバックエンドを反復横跳びしているなむゆです。
最近はAzureのサービスとの絡みも増えて、インプットが増えている気がします。嬉しいですね。
今回はそんなAzureのサービスと絡む.NETアプリ開発の中で一つ問題が起きて七転八倒したお話を共有します。

Azure Identity SDKとは

Azure Identity SDKとは、Azure Active Directoryを用いたトークン認証をサポートするSDKです。
他のAzure SDKを用いてAzureリソースにアクセスしようとする時の認証に使います。
このSDKに含まれるものの中でも特に優秀なのが「DefaultAzureCredentials」というクラスで、これはこのクラスのインスタンスを渡しておくだけでAzure環境上であればそのアプリがデプロイされたリソースのマネージドIDを用いて認証を行ってくれます。
さらに、ローカル環境でデバッグする際はその環境のVisual Studio、Visual Studio Code、あるいはコマンドコンソールでaz loginした際の認証情報を使って認証を行ってくれるので、Azure環境でしか動かないということもありません。
オンプレ環境であっても環境変数を見てくれるので死角なしです。
どうしてそうなっているかというと、このクラスの動作としてはその環境の認証情報がある場所を順に探索し、見つかればそれを使うというロジックになっているためです。
具体的な順番はDefaultAzureCredentialに書いてありますが、例えば一番最初は環境変数を確認しに行くのですがそこに必要な認証情報がなければ次はマネージドIDを探しに行きます。
それでも見つからなければ次は使用しているVisual Studioの認証情報を・・・といったように順に探しに行き、どこかで認証情報が見つかればそれを使う形になっています。
さらに、認証情報をappsettings等の環境情報として管理しなくてよくなるというメリットもあります。
ローカルではvisual studioやazure cliでログインしていれば、Azure上ではデプロイ先のリソースのマネージドIDを設定しておけばいいので、環境ごとに環境情報ファイルを書き換えて保存する手間が省けます。
今回も、それを用いて実装しようとしていました。

どんなエラーが起きたか

まずはローカルでデバッグだというわけで認証情報取得順の中ではAzure CLIの認証情報を使いたいため、別コンソールでaz loginしました。
.NETアプリケーションからAzure Keyvaultの接続する際、認証情報の取得をAzure Identity SDKのDefaultAzureCredentialクラスを使用して行おうとしました。
サンプルのコードはこちらで、大体そのサンプルコードの通りに実装してデバッグしてみました。
するとkeyvaultアクセスを行っている行で例外が発生しました。
以下がその例外のメッセージです。

Service request failed.  
Status: 401 (Unauthorized)  

Content:  
{"error":{"code":"Unauthorized","message":"AKV10032: Invalid issuer. Expected one of (keyvaultの属するテナントIDがいくつか入る), found (Visual StudioでログインしているユーザーのテナントID))"}}  

Headers:  
Cache-Control: no-cache  
Pragma: no-cache  
WWW-Authenticate: REDACTED  
x-ms-keyvault-region: (どこかのリージョン)  
x-ms-client-request-id: (clientIdが入っている)  
x-ms-request-id: REDACTED  
x-ms-keyvault-service-version: 1.2.205.0  
x-ms-keyvault-network-info: conn_type=Ipv4;addr=(IPアドレス);act_addr_fam=InterNetwork;  
X-Powered-By: REDACTED  
Strict-Transport-Security: REDACTED  
X-Content-Type-Options: REDACTED  
Date: (リクエストした時間)  
Content-Length: 345  
Content-Type: application/json; charset=utf-8  
Expires: -1  

エラーを見る限りkeyvaultにアクセスできる認証情報ではない認証情報が使われていました。
Azure CLIでログインした際の認証情報でもありません。
Visual Studio Enterprise関連の認証情報です。<- 大ヒント

原因

本当は話を引っ張ろうとも思ったのですが無理です。
前述のDefaultAzureCredential()のロジックとしてAzure CLIで取得した認証情報より先にVisual Studioでログインした情報を使用して認証しようとしていました。
この認証情報では目標のkeyvaultにはアクセスできないため、認証できないとしてエラーになっていました。

解決方法

「じゃあ途中の使いたくない認証情報を探しに行くのをスキップすればいいじゃん」という発想で「DefaultAzureCredential Exclude VisualStudio」等で調べたところ・・・ありました
DefaultAzureCredentialクラスをインスタンス化する際にOptionsとして ExcludeVisualStudioCredential = true を渡しておくことで、認証情報を探すフローからvisual studioのものを外してくれます。
他に、TokenCacheというらしいものもフローに含まれているらしい(ドキュメントに記載なし)ので、これも除外するようにした以下のコードにしたところAzure CLIの認証情報を使ってリソースにアクセスできました。

var client = new SecretClient(new Uri("KeyVaultのUrl"), new DefaultAzureCredential(new DefaultAzureCredentialOptions  
            {  
                ExcludeVisualStudioCredential = true,  
                ExcludeVisualStudioCodeCredential = true,  
                ExcludeSharedTokenCacheCredential = true  
            }  
        )  
    );  

おわりに

今回はAzureリソースにアクセスする際の認証情報の取得周りで少し詰まったことを共有しました。
同じ部分で詰まっている方の助けになれば幸いです~

参考