Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Comments on Credentials for multiple tenants with Azure.Identity
Post
Credentials for multiple tenants with Azure.Identity
Situation: I have an Azure account which has management permissions for various subscriptions in various directories (tenants). I have a GUI tool to do various management tasks whose details are irrelevant, and I'm updating this tool to the latest and supposedly greatest Microsoft auth library. What I want to do initially is to display a list of the subscriptions available, so that I can choose the one I want to administer.
The code below uses console output because I'm working with a minimal framework to test ideas.
Approach 1: "Works" but has appalling UX
using Azure.Core.Pipeline;
using Azure.Identity;
using Azure.ResourceManager;
...
var DeveloperSignOnClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; // From internal class Azure.Identity.Constants
var loginAuthOpt = new InteractiveBrowserCredentialOptions { TokenCachePersistenceOptions = new TokenCachePersistenceOptions { Name="listsubs" } };
var credential = new InteractiveBrowserCredential(null, DeveloperSignOnClientId, loginAuthOpt);
var options = new ArmClientOptions();
var pipeline = HttpPipelineBuilder.Build(options, new BearerTokenAuthenticationPolicy(credential, options.Scope));
var message = pipeline.CreateMessage();
message.Request.Uri.Reset(new Uri("https://management.azure.com/tenants?api-version=2019-11-01"));
message.Request.Headers.Add("Accept", "application/json");
pipeline.Send(message, default);
var tenantList = message.Response.Content.ToObjectFromJson<MyTenantList>();
foreach (var tenant in tenantList.value)
{
var newTenantCredential = new InteractiveBrowserCredential(tenant.tenantId, DeveloperSignOnClientId, loginAuthOpt);
var tenantPipeline = HttpPipelineBuilder.Build(options, new BearerTokenAuthenticationPolicy(newTenantCredential, options.Scope));
message = tenantPipeline.CreateMessage();
message.Request.Uri.Reset(new Uri("https://management.azure.com/subscriptions?api-version=2019-11-01"));
message.Request.Headers.Add("Accept", "application/json");
tenantPipeline.Send(message, default);
Console.Write("\t");
Console.WriteLine(message.Response.Content);
}
This successfully lists the subscriptions, but it pops up an initial authentication dialog to get the list of tenants and then a new authentication dialog per tenant. This would be cumbersome if I only had access to one tenant, but with half a dozen it's unacceptable.
Approach 2: seems like it should work, but doesn't
Based on how the older library worked, I expected that having authenticated I could get access tokens for the other tenants, so I tried an adapter:
public class TenantCredentialAdapter : TokenCredential
{
private readonly TokenCredential CredentialSource;
private readonly string TenantId;
public TenantCredentialAdapter(TokenCredential credentialSource, string tenantId)
{
CredentialSource = credentialSource ?? throw new ArgumentNullException(nameof(credentialSource));
TenantId = string.IsNullOrEmpty(tenantId) ? throw new ArgumentNullException(nameof(tenantId)) : tenantId;
}
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) =>
CredentialSource.GetToken(new TokenRequestContext(requestContext.Scopes, requestContext.ParentRequestId, requestContext.Claims, TenantId), cancellationToken);
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) =>
CredentialSource.GetTokenAsync(new TokenRequestContext(requestContext.Scopes, requestContext.ParentRequestId, requestContext.Claims, TenantId), cancellationToken);
}
I then replace newTenantCredential
in approach 1 with new TenantCredentialAdapter(credential, tenant.tenantId)
. Azure doesn't give me any 403s or other errors, but all of the lists of subscriptions are now
{"value":[],"count":{"type":"Total","value":0}}
where before some of them were non-empty.
Approach 3: not yet implemented
The best idea I have at the moment is to do approach 1 but to cache the list of tenants and subscriptions to disk. No authentication would be done until the user has selected the subscription. This would require the addition of a "Refresh" button alongside the list of subscriptions which would do the whole n+1 authentication process.
Surely there's a better way?
1 comment thread