@@ -229,15 +229,57 @@ TokenCredential runtimeCredential
229229 ) ;
230230 }
231231
232- public static TokenCredential CreateCredential ( IConfiguration config )
232+ /// <summary>
233+ /// Resolves the identity the backend authenticates as. Used by both the Key Vault and the
234+ /// runtime credential so they cannot disagree. Appsettings wins; AZURE_* is only a fallback.
235+ /// In AKS, AZURE_CLIENT_ID is injected from the <c>azure.workload.identity/client-id</c>
236+ /// annotation in robotics-infrastructure, which must match <c>AzureAd:ClientId</c>.
237+ /// </summary>
238+ private static ( string ? TenantId , string ? ClientId ) ResolveIdentity ( IConfiguration config )
233239 {
234- string ? tenantId = config [ "AzureAd:TenantId" ] ;
235- string ? clientId = config [ "AzureAd:ClientId" ] ;
236- string ? clientSecret = config [ "AzureAd:ClientSecret" ] ;
240+ return (
241+ config [ "AzureAd:TenantId" ] ?? config [ "AZURE_TENANT_ID" ] ,
242+ config [ "AzureAd:ClientId" ] ?? config [ "AZURE_CLIENT_ID" ]
243+ ) ;
244+ }
245+
246+ /// <summary>
247+ /// Options for the credential that reads Key Vault. It needs its own credential because
248+ /// Key Vault is read before <see cref="CreateCredential"/> can run — the client secret
249+ /// that method may use is itself stored there. DefaultAzureCredential keeps local
250+ /// development working via <c>az login</c>, pinned to <see cref="ResolveIdentity"/>.
251+ /// </summary>
252+ public static DefaultAzureCredentialOptions CreateKeyVaultCredentialOptions (
253+ IConfiguration config
254+ )
255+ {
256+ var ( tenantId , clientId ) = ResolveIdentity ( config ) ;
257+
258+ var options = new DefaultAzureCredentialOptions ( ) ;
259+
260+ if ( ! string . IsNullOrWhiteSpace ( tenantId ) )
261+ options . TenantId = tenantId ;
237262
238- tenantId ??= config [ "AZURE_TENANT_ID" ] ;
239- clientId ??= config [ "AZURE_CLIENT_ID" ] ;
240- clientSecret ??= config [ "AZURE_CLIENT_SECRET" ] ;
263+ if ( ! string . IsNullOrWhiteSpace ( clientId ) )
264+ options . WorkloadIdentityClientId = clientId ;
265+
266+ return options ;
267+ }
268+
269+ public static TokenCredential CreateCredential ( IConfiguration config )
270+ {
271+ var ( tenantId , clientId ) = ResolveIdentity ( config ) ;
272+ string ? clientSecret = config [ "AzureAd:ClientSecret" ] ?? config [ "AZURE_CLIENT_SECRET" ] ;
273+
274+ Console . WriteLine (
275+ $ "Backend identity: clientId={ clientId } (source: "
276+ + (
277+ config [ "AzureAd:ClientId" ] is not null
278+ ? "appsettings"
279+ : "AZURE_CLIENT_ID environment variable"
280+ )
281+ + ")"
282+ ) ;
241283
242284 var workloadOptions = new WorkloadIdentityCredentialOptions ( ) ;
243285 if ( ! string . IsNullOrWhiteSpace ( clientId ) )
@@ -247,7 +289,7 @@ public static TokenCredential CreateCredential(IConfiguration config)
247289
248290 var workloadIdentity = new WorkloadIdentityCredential ( workloadOptions ) ;
249291
250- bool allowUsingClientSecret = config . GetValue < bool > ( "AllowUsingClientSecret" ) ;
292+ bool allowUsingClientSecret = config . GetValue < bool > ( "AzureAd: AllowUsingClientSecret" ) ;
251293
252294 if (
253295 allowUsingClientSecret
0 commit comments