Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

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.

Post History

71%
+3 −0
Q&A 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 ir...

0 answers  ·  posted 3y ago by Peter Taylor‭

#1: Initial revision by user avatar Peter Taylor‭ · 2021-09-09T09:26:41Z (over 3 years ago)
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?**