When I was working with Azure.Identity.ClientSecretCredential
for authentication, I noticed something interesting—it doesn't require me to manually grab a access token and add it to the header. The ClientSecretCredential
takes care of fetching the token for me. Pretty cool, right?
Here's how it works in code
using Azure.Identity;
using Microsoft.Graph;
// Use ClientSecretCredential for authentication
var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
// Create a GraphServiceClient instance
var graphClient = new GraphServiceClient(credential);
// Make the API call
var users = await graphClient.Users.GetAsync();
Now, if you were to use MSAL to get the token and then manually call the Graph API with HttpClient
, it would be a bit more complex and the data you'd get back would be in raw JSON format. On the other hand, when using the Graph SDK, the data comes back nicely wrapped in models that are easier to work with.
// Create Confidential Client
var clientApp = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri($"https://login.microsoftonline.com/{tenantId}"))
.Build();
// Get Token
var authResult = await clientApp.AcquireTokenForClient(scopes).ExecuteAsync();
string token = authResult.AccessToken;
// Call Graph API
var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await http.GetAsync("https://graph.microsoft.com/v1.0/users");
var content = await response.Content.ReadAsStringAsync();
So, the question is: Where does the token handling happen in the ClientSecretCredential
approach?
After digging deeper into the code, I found out that the ClientSecretCredential.GetTokenAsync()
method is actually the one fetching the token for us.
public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken = default)
{
using CredentialDiagnosticScope scope = _pipeline.StartGetTokenScope("ClientSecretCredential.GetToken", requestContext);
try
{
var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, AdditionallyAllowedTenantIds);
// Here it fetches the token
AuthenticationResult result = await Client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, requestContext.Claims, requestContext.IsCaeEnabled, true, cancellationToken).ConfigureAwait(false);
return scope.Succeeded(result.ToAccessToken());
}
catch (Exception e)
{
throw scope.FailWrapAndThrow(e);
}
}
Then, in the AzureIdentityAuthenticationProvider
class, it adds the token to the request header in the AuthenticationRequestAsync()
method.
public async Task AuthenticateRequestAsync(RequestInformation request, Dictionary<string, object>? additionalAuthenticationContext = default, CancellationToken cancellationToken = default)
{
if(request == null) throw new ArgumentNullException(nameof(request));
if(additionalAuthenticationContext != null &&
additionalAuthenticationContext.ContainsKey(ClaimsKey) &&
request.Headers.ContainsKey(AuthorizationHeaderKey))
request.Headers.Remove(AuthorizationHeaderKey);
if(!request.Headers.ContainsKey(AuthorizationHeaderKey))
{
var token = await AccessTokenProvider.GetAuthorizationTokenAsync(request.URI, additionalAuthenticationContext, cancellationToken);
// Here, it adds the token to the header
if(!string.IsNullOrEmpty(token))
request.Headers.Add(AuthorizationHeaderKey, $"Bearer {token}");
}
}
In the background, when you create a ClientSecretCredential()
, it actually sets up an HttpClient
for you. So when you call await graphClient.Users.GetAsync()
, it triggers HttpClient.SendAsync()
, and that's when the magic happens—AuthenticateRequestAsync()
is called, the token is fetched, and then added to the request header.
Summary
- When you call
await graphClient.Users.GetAsync()
, theGraphServiceClient
triggersHttpClient.SendAsync()
- Inside
SendAsync()
,AuthenticateRequestAsync()
is called - In
AuthenticateRequestAsync()
, it fetched the token and adds it to the header - Then, the HTTP request is sent to the Graph API
Job done☑️
Thanks for reading!
If you like this article, please don't hesitate to click the heart button ❤️
or follow my GitHub I'd appreciate it.
Top comments (0)