Skip to content

Multi tenant client_credential use

Bogdan Gavril edited this page May 31, 2021 · 27 revisions

Pattern for using MSAL for client credential flow in multi-tenant services

Decision point - Microsoft.Identity.Web or Microsoft.Identity.Client (MSAL)?

If you use ASP.NET Core, you are encouraged to adopt Microsoft.Indentity.Web, which provides a higher level API over token acquisition and has better defaults. See Is MSAL.NET right for me?

Decision point - token caching

MSAL maintains a token cache which grows with each token acquired. MSAL manages token lifetimes in a smart way, so you should use its cache. If your service needs to call N tenants, there will be potentially N tokens in MSAL's cache, each around 2Kb in size. N can be very large, there are > 1 million tenants in AAD and this number is growing.

Anti-pattern - do it yourself

// each ConfidentiClientApplication object maintains its own internal cache 
ConfidentiClientApplication cca = ConfidentiClientApplicationBuilder
              .WithCertificate(x509certificate)
              .Create("client_id"); 

var result = cca.AcquireTokenForClient().WithSendX5C(true) // for SNI
                      .ExecuteAsync();

// use result.AccessToken and result.ExpiresOn to cache your own token

// Problem: cca goes out of scope and MSAL's internal cache is lost

The problem is that you'd be missing out on the pro-active refresh feature MSALs implement. With this feature, services receive tokens available for a long time (12h) and are instructed to refresh them for half that time (6h). This ensures that even if AAD / ESTS goes down, your service has a fresh token that is available for a long time.

Pattern 1 - let MSAL do it

Note: does not work for OBO.

// When your service starts, create a singleton / static ConfidentialClientApplication
static ConfidentiClientApplication s_cca = ConfidentiClientApplicationBuilder
              .WithCertificate(x509certificate)
              .Create("client_id"); 

// Then, whenever you need a token, specify the authority
var result = await s_cca .AcquireTokenForClient("scope_for_downstream_api")
                      .WithAuthority("https://login.microsoft.com/<tenant_id>") // do NOT use common or organizations here
                      .WithSendX5C(true) // for SNI
                      .ExecuteAsync();

// You can monitor if the cache was hit
bool cacheHit = result.AuthenticationResult.AuthenticationResultMetadata.TokenSource == TokenSource.Cache;
  • MSAL does not evict items from the cache, and at 2KB size per token, you could eventually go out of memory.
  • MSAL 4.30+ only
  • But if you are migrating from ADAL and haven't run out of memory, this will have the same memory profile.
  • If you need to use different client IDs, then maintain a dictionary of <client_id> -> ConfidentialClientApplication

Pattern 2 - take control of the cache

This is the North Star and works for OBO as well.

// In your app initialization define a cache 
// See https://github.com/Azure-Samples/active-directory-dotnet-v1-to-v2/blob/master/ConfidentialClientTokenCache/Program.cs#L83 for several implementations 
static var s_cache = InMemoryWithLRU / Redis / SqlServer / L1InMemroy_L2Distributed / etc.

// Then when you need a token
var cca = ConfidentialClientApplicationBuilder.Create("client_id")
               .WithAuthority("https://login.microsoftonline.com/<tenantid>")
               .WithCertificate(certificate)
               .Build();

s_cache.Initialize(cca.AppTokenCache); // configure the CCA to use your token cache

var result = await app.AcquireTokenForClient(new[] {"https://graph.windows.net/.default"})
                 .WithSendX5C(true) // SNI
                 .ExecuteAsync();

  • Microsoft.Identity.Web project has several high performance token cache implementations
  • If you are using ASP.NET Core, consider using Microsoft.Identity.Web - it's a higher level API that takes care of many aspects for you
  • If not, see this simple sample that shows how to use a token cache from Microsoft.Identity.Web in ANY application
  • For best performance, in-memory caching with LRU policies plus optional distributed cache, use the L1/L2 cache or the InMemoryCache.

Getting started with MSAL.NET

Acquiring tokens

Desktop/Mobile apps

Web Apps / Web APIs / daemon apps

Advanced topics

News

FAQ

Other resources

Clone this wiki locally