Tuesday, February 2, 2021

Validating azure directory token programmatically and getting details of user in C#

While creating an API to consume in our application or want to interact between applications. One of the key issue we need to address is to pass identity of authenticated user from our applications to our new applications or calling APIs. In my case it was a Azure function APIs which I need to authenticate before returning few details. Here I am referring how can we validate a passed a token in our API calls or app code. Here I am using a token issues by Azure AD so validating token using Microsoft namespaces. Below is the custom method I have written which we can call for validating token and get property of user for control. Here  I am returning email address of user:

public static string ValidateTokenAndGetEmail(string jwtToken, string appIssuer, string appAudience, string appSigningKey)
{
string email = "Invalid token.";
IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(appSigningKey, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = AsyncHelper.RunSync(async () => await configurationManager.GetConfigurationAsync(CancellationToken.None));
TokenValidationParameters validationParameters =
new TokenValidationParameters
{
ValidateLifetime = true, 
ValidIssuer = appIssuer,
ValidAudiences = new[] { appAudience },
IssuerSigningKeys = openIdConfig.SigningKeys
};
SecurityToken validatedToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var user = handler.ValidateToken(jwtToken, validationParameters, out validatedToken);
// The ValidateToken method above will return a ClaimsPrincipal. Get the user ID from the NameIdentifier claim
// (The sub claim from the JWT will be translated to the NameIdentifier claim)
email = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
if (string.IsNullOrEmpty(email)) { {
                               email = "Invalid token.";
}
return email;
}


Let me talk bit about this parameters in function:

"jwt": "provide your token here, you can pass it through request header"

"AppTokenIssuer": "https://login.microsoftonline.com/<tid>/v2.0" --- tid needs to be cheked from azure admin. 

"AppTokenAudience": "<aud>"--- aud needs to be cheked from azure admin. 

"AppSigningKey": "https://login.microsoftonline.com/<tid>/.well-known/openid-configuration" --- tid needs to be cheked from azure admin. 

Namespaces we would need for these classes:

Microsoft.IdentityModel.Protocols;

Microsoft.IdentityModel.Protocols.OpenIdConnect

Microsoft.IdentityModel.Tokens

System.IdentityModel.Tokens.Jwt

System.Security.Claims

System.Collections.Generic

You need to add this additional class for execute task asynchronously:

internal static class AsyncHelper
{
private static readonly TaskFactory TaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

public static void RunSync(Func<Task> func)
{
TaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
}
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
return TaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
}
}