Query/Question
Thanks for the super helpful library 🙇♂️
The XML docs for DefaultAzureCredential.GetTokenAsync mention the following:
This method is called by Azure SDK clients. It isn't intended for use in application code.
While I understand the main use of TokenCredential.GetTokenAsync is for the various Azure SDK clients, we found ourselves calling it manually to get tokens for Azure SQL. See this yet to be published blog post 👉 https://github.com/mderriey/mderriey.github.io/blob/azure-sql-managed-identity/_posts/2020-07-11-connect-to-azure-sql-with-aad-and-managed-identities.md#connecting-to-azure-sql-using-azure-active-directory-authentication
The portion of the XML docs aforementioned got people on my team asking if using this class directly was something we should be doing.
If you think it's fine, could we look at updating the XML docs?
I we shouldn't be using it directly, would you mind pointing us in the right direction?
Environment:
dotnet --info output for .NET Core projects): Not relevantNice blog! :) Let me know when you publish it.
Thanks @Petermarcu, it's now published over at https://mderriey.com/2020/07/17/connect-to-azure-sql-with-aad-and-managed-identities/
@mderriey Thanks for reaching out with this question, and thanks for the great blog post! Your use of the GetTokenAsync method here is perfectly valid. The statement from the XML docs you mentioned above, was meant to discourage people from calling GetTokenAsync directly in the most common case that they are using a client from our SDK which supports authenticating using a TokenCredential. However, it's probably too strongly worded, as we didn't intend to discourage usages such as what you demonstrate in your blog post. I think we should probably re-word this a bit. Did you have any suggestions in mind?
Hey @schaabs, thanks for getting back to me on this!
Right now, the docs read:
This method is called by Azure SDK clients. It isn't intended for use in application code.
Here are a few suggestions:
TokenCredential and doesn't need to be used in application code.Naming Phrasing is hard, right? 😂
I'm also considering to use the chaining logic from ManagedIdentityCredential + ClientSecretCredential from this library instead of ConfidentialClientApplication (from Microsoft.Identity.Client) to get a token in a client library for connecting to a Azure AD protected WebApi.
But does the ManagedIdentityCredential and ConfidentialClientApplication also cache the token?
@StefH this is good question. As of our 1.2.0 release the ClientSecretCredential does cache acquired tokens, but the ManagedidentityCredential does not. We do have plans to update the ManagedIdentityCredential so that it also caches acquired tokens, but this requires some changes from Microsoft.Identity.Client so it probably won't happen until the end of this year, early next year.
In general if you are calling GetTokenAsync on an implementation of TokenCredential directly, you shouldn't assume it caches the token. This is why the method returns the AccessToken structure which not only contains the Token but also the ExpiresOn time.
@schaabs Thanks for explaining. In that case I'll cache the tokens using IMemoryCache.
My code looks like:
``` c#
internal class AccessTokenService : IAccessTokenService
{
private const string Key = "StefAccessTokenService_AccessToken";
private readonly Lazy<TokenRequestContext> _tokenRequestContext;
private readonly Lazy<TokenCredential> _tokenCredential;
private readonly ILogger<AccessTokenService> _logger;
private readonly IMemoryCache _cache;
public AccessTokenService(ILogger<AccessTokenService> logger, IOptions<AuditClientMicrosoftIdentityClientOptions> options, IMemoryCache cache)
{
_logger = logger;
_cache = cache;
_tokenRequestContext = new Lazy<TokenRequestContext>(() => new TokenRequestContext(new[] { $"{options.Value.Resource}/.default" }));
_tokenCredential = new Lazy<TokenCredential>(() => CreateTokenCredential(options.Value));
}
public async Task<string> GetTokenAsync(CancellationToken cancellationToken)
{
var cachedAccessToken = await
_cache.GetOrCreateAsync(Key, async entry =>
{
_logger.LogInformation("getting new token");
var accessToken = await _tokenCredential.Value.GetTokenAsync(_tokenRequestContext.Value, cancellationToken).ConfigureAwait(false);
entry.AbsoluteExpiration = accessToken.ExpiresOn;
return accessToken;
}).ConfigureAwait(false);
_logger.LogInformation("token : " + cachedAccessToken.Token);
_logger.LogInformation("expire : " + cachedAccessToken.ExpiresOn.ToLocalTime());
return cachedAccessToken.Token;
}
private TokenCredential CreateTokenCredential(AuditClientMicrosoftIdentityClientOptions optionsValue)
{
return new ChainedTokenCredential(
new ManagedIdentityCredential(optionsValue.ClientId),
new ClientSecretCredential(optionsValue.TenantId, optionsValue.ClientId, optionsValue.ClientSecret)
);
}
}
```