-
Notifications
You must be signed in to change notification settings - Fork 343
Multi tenant client_credential use
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?
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.
// Problem: cca goes out of scope and MSAL's internal cache is lost
string GetAccessToken()
{
// 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();
}
If you keep calling this GetAccessToken
above, you'll always make an HTTP request to AAD.
If you manage the token expiry on your own, you'll 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.
This does not scale well for web sites or web apis that deal with users (!). It should only be used with AcquireTokenForClient (i.e. app to app) only.
static async Task<string> GetTokenAsync
{
var cca = ConfidentiClientApplicationBuilder
.WithCertificate(x509certificate)
.WithAuthority($"https://login.microsoftonline.com/{tenant_id}") // do not use common, unless you override the tenant at the request level
.WithCacheOptions(CacheOptions.EnableSharedCacheOptions) // cache is shared between all ConfidentiClientApplication objects
.Create("client_id");
var result = await cca .AcquireTokenForClient("scope_for_downstream_api")
.WithTenantId(tid) // optional, if you want to reuse the cca object
.ExecuteAsync();
// You can monitor if the cache was hit
bool cacheHit = result.AuthenticationResult.AuthenticationResultMetadata.TokenSource == TokenSource.Cache;
return result.AccessToken;
}
- MSAL does not evict items from the cache, and at 2KB size per token, you could eventually go out of memory.
- In
AcquireTokenForClient()
, the number of tokens = number of tenants x number of resources you need to access. There are some ~1M tenants in AAD, and apps typically access a single resource in each one. So the token cache does not grow beyond ~2-3GB of memory. - Can also be used by web sites / web apis that see little traffic.
- MSAL 4.37+ only
- If you need to use different client IDs, then maintain a dictionary of <client_id> -> ConfidentialClientApplication
L2 caching works for user flows (authorization code and OBO).
// 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
async string GetAccessToken()
{
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();
// You can monitor if the cache was hit
bool cacheHit = result.AuthenticationResult.AuthenticationResultMetadata.TokenSource == TokenSource.Cache;
return result.AccessToken;
}
- L2 cachingserializes / deserializes the cache using JSON, so it is slower than in-memory caching.
- Microsoft.Identity.Web project has several high performance token cache implementations.
- If not using ASP.NET Core, see this simple sample that shows how to use a token cache from Microsoft.Identity.Web in ANY application
- Distributed apps are encouraged to use an L1 / L2 cache, where L2 is distributed and shared between all pods (e.g. Redis). See L1/L2 cache.
- Home
- Why use MSAL.NET
- Is MSAL.NET right for me
- Scenarios
- Register your app with AAD
- Client applications
- Acquiring tokens
- MSAL samples
- Known Issues
- AcquireTokenInteractive
- WAM - the Windows broker
- .NET Core
- Maui Docs
- Custom Browser
- Applying an AAD B2C policy
- Integrated Windows Authentication for domain or AAD joined machines
- Username / Password
- Device Code Flow for devices without a Web browser
- ADFS support
- Acquiring a token for the app
- Acquiring a token on behalf of a user in Web APIs
- Acquiring a token by authorization code in Web Apps
- High Availability
- Token cache serialization
- Logging
- Exceptions in MSAL
- Provide your own Httpclient and proxy
- Extensibility Points
- Clearing the cache
- Client Credentials Multi-Tenant guidance
- Performance perspectives
- Differences between ADAL.NET and MSAL.NET Apps
- PowerShell support
- Testing apps that use MSAL
- Experimental Features
- Proof of Possession (PoP) tokens
- Using in Azure functions
- Extract info from WWW-Authenticate headers
- SPA Authorization Code