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

Dashboard
Notifications
Mark all as read
Q&A

Credentials for multiple tenants with Azure.Identity

+3
−0

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?

Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

0 answers

Sign up to answer this question »