Keywords: Azure AD | Security Token Validation | JWT | C# | OAuth 2.0
Abstract: This article provides a comprehensive exploration of Azure Active Directory (Azure AD) security token validation, focusing on the structural verification of JSON Web Tokens (JWT) and claims inspection. Through C# code examples, it demonstrates how to retrieve public keys from Azure AD, configure TokenValidationParameters, and use JwtSecurityTokenHandler for token validation. The article also covers validation of iss, aud, and nbf claims in single-tenant applications, along with adaptations for .NET Core 2.0, offering developers thorough technical guidance.
Core Mechanisms of Azure AD Security Token Validation
Security tokens issued by Azure Active Directory (Azure AD) typically adopt the JSON Web Token (JWT) format, an open standard (RFC 7519) for securely transmitting information between parties. A JWT consists of three parts: header, payload, and signature. The header contains the token type and signing algorithm, the payload includes claims, and the signature verifies the token's integrity and origin.
Two-Step Validation Process
Validating an Azure AD token involves two critical steps: first, verifying the signature to ensure the token was issued by Azure AD and has not been tampered with; second, validating claims to meet business logic requirements. Signature verification is performed using public keys, which can be retrieved from Azure AD's OpenID Connect configuration endpoint. Claims validation includes checking standard claims such as iss (issuer), aud (audience), and nbf (not before) to ensure the token is applicable to the current application and has not expired.
C# Implementation: Retrieving and Validating Tokens
The following code example demonstrates how to retrieve an Azure AD token and validate it. First, obtain the token using client credentials:
string tenantName = "mytest.onmicrosoft.com";
string authString = "https://login.microsoftonline.com/" + tenantName;
AuthenticationContext authenticationContext = new AuthenticationContext(authString, false);
string clientId = "fffff33-6666-4888-a4tt-fbttt44444";
string key = "123v47o=";
ClientCredential clientCred = new ClientCredential(clientId, key);
string resource = "http://mytest.westus.cloudapp.azure.com";
Task<AuthenticationResult> authenticationResult = authenticationContext.AcquireTokenAsync(resource, clientCred);
string token = authenticationResult.Result.AccessToken;After retrieving the token, use JwtSecurityTokenHandler for validation. The following method fetches public keys from the Azure AD v2 endpoint and validates the token:
public JwtSecurityToken Validate(string token)
{
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningTokens = config.SigningTokens,
ValidateLifetime = false
};
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
var result = tokenHandler.ValidateToken(token, validationParameters, out jwt);
return jwt as JwtSecurityToken;
}In practical applications, enable ValidateAudience, ValidateIssuer, and ValidateLifetime, and set appropriate values for ValidAudience and ValidIssuer. For example, in a single-tenant application, ValidIssuer is typically set to "https://sts.windows.net/{tenantId}/", and ValidAudience to the application ID.
Claims Validation and Business Logic
Beyond standard claims, developers must validate custom claims based on business needs. For instance, in a Web API, check the scope claim to ensure the user has appropriate permissions:
if (ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value != "user_impersonation")
{
throw new HttpResponseException(new HttpResponseMessage {
StatusCode = HttpStatusCode.Unauthorized,
ReasonPhrase = "The Scope claim does not contain 'user_impersonation' or scope claim not found"
});
}This ensures that only tokens with the user_impersonation scope can access protected resources.
OWIN Integration and .NET Core Adaptation
For projects using OWIN (Open Web Interface for .NET), the validation process is more streamlined. The UseWindowsAzureActiveDirectoryBearerAuthentication middleware automatically handles token validation:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"]
});In .NET Core 2.0, adjust the validation method to accommodate API changes:
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
stsDiscoveryEndpoint,
new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidAudience = "some audience",
ValidIssuer = "some issuer",
ValidateAudience = true,
ValidateIssuer = true,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = true
};Key changes include using the OpenIdConnectConfigurationRetriever constructor and replacing IssuerSigningTokens with IssuerSigningKeys.
Security Best Practices
When validating tokens, adhere to the following best practices: always enable lifetime validation to prevent replay attacks; cache public keys to reduce network calls; regularly update configurations to handle Azure AD key rotation. Additionally, for production environments, it is recommended to dynamically fetch configurations from Azure AD's metadata endpoints rather than hardcoding values.
By combining signature verification and claims inspection, developers can ensure the security and applicability of Azure AD tokens, thereby protecting Web APIs and applications from unauthorized access. The code examples and methods provided in this article offer a solid foundation for practical implementation, which can be adapted and extended based on specific requirements.