I used to have a reference to Microsoft.IdentityModel.Tokens.JWT and everything was working fine.
I updated to use the new System.IdentityModel.Tokens.Jwt but nothing seems to work now. It cannot find the ValidateToken method of the JwtSecurityTokenHandler and the TokenValidationParameters have no AllowedAudience, SigningToken or ValidateExpiration properties.
What am I missing here? Can anyone provide with a working sample of a JWT validation with this?
My "old" code :
private static void ValidateJwt(string jwt)
{
var handler = new JWTSecurityTokenHandler();
var validationParameters = new Microsoft.IdentityModel.Tokens.JWT.TokenValidationParameters()
{
AllowedAudience = "https://my-rp.com",
//SigningToken = new BinarySecretSecurityToken(Convert.FromBase64String(myBase64Key)),
SigningToken = new X509SecurityToken(
X509
.LocalMachine
.My
.Thumbprint
.Find("UYTUYTVV99999999999YTYYTYTY88888888", false)
.First()),
ValidIssuer = "https://my-issuer.com/trust/issuer",
ValidateExpiration = true
};
try
{
var principal = handler.ValidateToken(jwt, validationParameters);
}
catch (Exception e)
{
Console.WriteLine("{0}\n {1}", e.Message, e.StackTrace);
}
Console.WriteLine();
}
After a lot of research and tests, I finally found that some properties names for TokenValidationParameters had changed and JwtSecurityTokenHandler.ValidateToken() method signature too.
So here's the modified working version of the above code.
private static void ValidateJwt(string jwt)
{
var handler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters()
{
ValidAudience = "https://my-rp.com",
IssuerSigningTokens = new List<X509SecurityToken>() { new X509SecurityToken(
X509
.LocalMachine
.My
.Thumbprint
.Find("UYTUYTVV99999999999YTYYTYTY88888888", false)
.First()) },
ValidIssuer = "https://my-issuer.com/trust/issuer",
CertificateValidator = X509CertificateValidator.None,
RequireExpirationTime = true
};
try
{
SecurityToken validatedToken;
var principal = handler.ValidateToken(jwt, validationParameters, out validatedToken);
}
catch (Exception e)
{
Console.WriteLine("{0}\n {1}", e.Message, e.StackTrace);
}
Console.WriteLine();
}
And for the reference, the JwtSecurityTokenHandler lives in the System.IdentityModel.Tokens namespace. Don't forget to add the package "JSON Web Token Handler For the Microsoft .Net Framework 4.5" (version 4.0.0 at the time I write theses lines).
Hope it can save a few hours of search for some of you guys!
Related
I am trying to write unit tests for authentication logic implemented using Azure AD client credentials flow using MOQ.
The first test case is to check that if the "Audience" is valid. I am trying to mock claim to set up the "aud" or "appId" claims using ClaimTypes but not able to find anything like ClaimTypes.Aud
var identity = new ClaimsIdentity(new Claim[] {
new Claim(ClaimTypes.Name, "Sahil")
});
var mockPrincipal = new Mock<ClaimsPrincipal>(identity);
mockPrincipal.Setup(x => x.Identity).Returns(identity);
mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);
How can I set up the "aud" and "appId" claims in C#
OR
Just setup mockPrincipal so that when it tries to check if "aud" is valid it returs false.
I am trying to write unit tests for the below code.
public void Authenticate(JwtBearerOptions options)
{
_configuration.Bind("AzureAD", options);
options.TokenValidationParameters.ValidateAudience = true;
options.TokenValidationParameters.ValidateIssuerSigningKey = true;
options.TokenValidationParameters.ValidateIssuer = true;
options.Events ??= new JwtBearerEvents();
var existingHandlers = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
string appId = GetAppIdFromToken(context);
bool isAllowed = await CheckAppIdIsAllowedAsync(context, appId);
if (isAllowed)
{
_logger.LogInformation($"[{nameof(Authenticate)}] AppId in allow list");
}
else
{
_logger.LogError($"[{nameof(Authenticate)}] AppId {appId} not in allowed list");
}
await Task.CompletedTask.ConfigureAwait(false);
};
options.Events.OnTokenValidated += existingHandlers;
}
private string GetAppIdFromToken(TokenValidatedContext context)
{
string appId = context.Principal.Claims.FirstOrDefault(x => x.Type == "appid" || x.Type == "azp")?.Value;
return appId;
}
private async Task<bool> CheckAppIdIsAllowedAsync(TokenValidatedContext context, string appId)
{
IEnumerable<string> AllowedApps = _configuration.GetSection("AllowedAppPrincipals").Get<string[]>();
var FoundAppId = AllowedApps.FirstOrDefault(a => a == appId);
if (FoundAppId == null)
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
context.Response.ContentType = "application/json";
const string message = "{\"error\" : \"Unacceptable app principal\"}";
byte[] arr = Encoding.ASCII.GetBytes(message);
await context.Response.BodyWriter.WriteAsync(arr);
context.Fail(message);
return false;
}
return true;
}
How to mock aud and appId claims with Moq?
I tried to reproduce how to get audience invalid in my environment.
It usually happens when the issuer endpoint is different or scope is not given correctly or different scope than what is intended is mentioned.
https://login.microsoftonline.com/xx/oauth2/v2.0/token
Here i gave scope for api other than microsoft graph.
But next tep i am calling graph endpoint and so i am getting invalid audience error .
https://graph.microsoft.com/v1.0/users/xxx
To check for mocking test , you can consider below point as direct claims for "aud" audience in c# couldn't be obtained AFAIK.
Aud claim is Application ID URI or GUID Identifies the intended
audience of the token. In v2.0 tokens, audience must be client ID
of the API whereas in v1.0 tokens, it can be the client ID or the
resource URI used in the request.
One way to validate it is to check following way with issuer/audience
You can give custom values according to the authorization endpoint
Code:
string AUDIENCE = "<GUID of your Audience according to the app>";
string TENANT = "<GUID of your Tenant>";
private static async Task<SecurityToken> validateJwtTokenAsync(string token)
{
// URL based on your AAD-TenantId
var stsDiscoveryEndpoint = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", TENANT);
//To Get tenant information
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint)
// Get Config from AAD:
var config = await configManager.GetConfigurationAsync();
// Validate token:
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidAudience = AUDIENCE,
ValidIssuer = config.Issuer,
IssuerSigningTokens = config.SigningTokens,
CertificateValidator = X509CertificateValidator.ChainTrust,
};
var validatedToken = (SecurityToken)new JwtSecurityToken();
tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
return validatedToken;
}
Or
var TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
....
ValidIssuer = configuration["JwtAuthentication:Issuer"],
ValidAudience = configuration["JwtAuthentication:Audience"]
};
So from validation parameters, you can get if that we that audience was valid or not, it majorly occurs when issuer is different from what we expected or when scopes are not correct.
You can try get those validate as claims to check for mocking
Snippent below taken from TokenValidationParameters.AudienceValidator, System.IdentityModel.Tokens C# (CSharp) Code Examples - HotExamples
public virtual ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken
validatedToken)
{
if (string.IsNullOrWhiteSpace(securityToken))
{
throw new ArgumentNullException("securityToken");
}
if (validationParameters == null)
{
throw new ArgumentNullException("validationParameters");
}
if (validationParameters.ValidateAudience)
{
if (validationParameters.AudienceValidator != null)
{
if
(!validationParameters.AudienceValidator(jwt.Audiences, jwt,
validationParameters))
{
throw new SecurityTokenInvalidAudienceException(string.Format(CultureInfo.InvariantCulture,
ErrorMessages.IDX10231, jwt.ToString()));
}
}
else
{
this.ValidateAudience(jwt.Audiences, jwt,
validationParameters);
}
}
ClaimsIdentity identity = this.CreateClaimsIdentity(jwt, issuer,
validationParameters);
if (validationParameters.SaveSigninToken)
{
identity.BootstrapContext = new
BootstrapContext(securityToken);
}
validatedToken = jwt;
return new ClaimsPrincipal(identity);
}
Also check Reference :c# - How to mock ConfigurationManager.AppSettings with moq - Stack Overflow
I have an asp.net core application using json web tokens for authentication, this worked fine when my user Id was a string, but stopped working after changing to an int.
My IdentityUser type was originally this
public class AppUser : IdentityUser
{
//other properties...
}
I updated this;
public class AppUser : IdentityUser<int>
{
//other properties...
}
I changed my Startup configuration from this;
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtIssuerOptions.Issuer;
options.Audience = jwtIssuerOptions.Audience;
options.SigningCredentials = jwtIssuerOptions.SigningCredentials;
});
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtIssuerOptions.Issuer,
ValidateAudience = true,
ValidAudience = jwtIssuerOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(configureOptions =>
{
configureOptions.ClaimsIssuer = jwtIssuerOptions.Issuer;
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
});
// add identity
var builder = services.AddIdentityCore<AppUser>(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
o.SignIn.RequireConfirmedEmail = true;
});
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
To this;
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtIssuerOptions.Issuer;
options.Audience = jwtIssuerOptions.Audience;
options.SigningCredentials = jwtIssuerOptions.SigningCredentials;
});
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtIssuerOptions.Issuer,
ValidateAudience = true,
ValidAudience = jwtIssuerOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(configureOptions =>
{
configureOptions.ClaimsIssuer = jwtIssuerOptions.Issuer;
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
});
// add identity
var builder = services.AddIdentity<AppUser, IdentityRole<int>>(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
o.SignIn.RequireConfirmedEmail = true;
});
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole<int>), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
I use a the following method to generate the token, this is unchanged between implementations.
public async Task<string> GenerateEncodedToken(string userName, ClaimsIdentity identity)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userName),
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
identity.FindFirst(JwtClaimIdentifiers.Rol),
identity.FindFirst(JwtClaimIdentifiers.Id)
};
// Create the JWT security token and encode it.
var jwt = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
notBefore: _jwtOptions.NotBefore,
expires: _jwtOptions.Expiration,
signingCredentials: _jwtOptions.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return encodedJwt;
}
And finally this method is used to create the claims identity;
public ClaimsIdentity GenerateClaimsIdentity(string userName, int id)
{
return new ClaimsIdentity(new GenericIdentity(userName, "Token"), new[]
{
new Claim(JwtClaimIdentifiers.Id, id.ToString(), ClaimValueTypes.Integer32),
});
}
User.Identity.IsAuthenticated is now always false when validating and the ClaimsIdentity appears to be empty of the information I saw before converting my AppUser from a string ID type to an int Id type.
The best theory I have so far is that the problem may be related to serialisation/deserialisation of the token values but I'm clutching at straws and have little idea how I might debug it.
What have I missed?
Nothing glaring at me in your code (it looks like the primary difference is you swapped from AddIdentity to AddIdentityCore?), but I find with JWT auth issues, it really helps to run the app in debug mode and add handlers for JwtBearerEvents.
Add this to your Startup .AddJwtBearer call:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// ...
// Add this at the end:
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
// Put a breakpoint here
System.Console.WriteLine(context.Exception);
return Task.CompletedTask;
},
};
})
If you run in debug, will be able to inspect the context.Exception:
This should give you a better explanation of why JWT authentication is failing.
Taking Connor Low's advice I supplied delegates to all the JwtBearerOptions events. None of these where hit, indicating that my setup was insufficient and that the authentication scheme I had configured was not being used at all.
I reverted to addIdentityCore
var builder = services.AddIdentityCore<AppUser>(o =>
Although I could no longer specify that I was using IdentityRole<int> instead of the default like this;
var builder = services.AddIdentity<AppUser, IdentityRole<int>>(o =>
This seemed to be unnecessary provided I had the following line;
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole<int>), builder.Services);
With these changes in place authentication worked and the breakpoints I had added at Connor Low's suggestion were being hit.
I want to get my Azure B2C AD access token from the httpContext or similar, but I am using .NET framework 4.7.2. If I was using .Net core, I would use HttpContext.Authentication.GetTokenAsync() .
Background
I am using OpenIdConnect 4.1.0.
My OpenIdConnectAuthenticationOptions looks like this:
private OpenIdConnectAuthenticationOptions CreateOptionsFromSiteConfig(B2CConfig config)
{
OpenIdConnectAuthenticationOptions options = new OpenIdConnectAuthenticationOptions();
options.MetadataAddress = string.Format(_aadInstance, _tenant, config.Policy);
options.AuthenticationType = [B2CAD-POLICY-name];
options.AuthenticationMode = AuthenticationMode.Passive;
options.RedirectUri = config.AzureReplyUri;
options.PostLogoutRedirectUri = config.LogoutRedirectUri;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "emails"
};
options.SaveTokens = true;
options.RedeemCode = true;
var identityProvider = GetIdentityProvider();
options.Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = AuthenticationFailed,
RedirectToIdentityProvider = notification =>
{
return Task.FromResult(notification.ProtocolMessage.UiLocales = config.UiLocale ?? string.Empty);
},
SecurityTokenValidated = notification =>
{
notification.AuthenticationTicket.Identity.AddClaim(new Claim("idp", "azureadb2c"));
// transform all claims
ClaimsIdentity identity = notification.AuthenticationTicket.Identity;
notification.AuthenticationTicket.Identity.ApplyClaimsTransformations(new TransformationContext(FederatedAuthenticationConfiguration, identityProvider));
return Task.CompletedTask;
},
};
options.ClientId = config.ClientId;
options.Scope = "openid [api-scope-here]";
options.ResponseType = "id_token token";
return options;
}
There is multiple policies with the same name (i.e. multiple AuthenticationTypes with same name).
So far, I have found several suggestions, where the most promising one suggested:
var result = await owinContext.Authentication.AuthenticateAsync([B2CAD-POLICY-name]));
string token = result.Properties.Dictionary["access_token"];
However, the result is always NULL, eventhough I have verified that the b2cad policy is actually present in the OwinContext.
Any help is much appreciated!
I've been tasked to validate a JWT token that's been encoded using the PS256 algorithm and for the last two days I've been having trouble with it. I lack knowledge on this subject and I've been chipping away slowly at the problem trying different solutions.
// Encoded
eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZDEyMzQifQ.eyJpc3MiOiJmb28uYmFyLnRlc3Rpc3N1ZXIiLCJleHAiOjE1NTEyMDEwNjgsImF0X2hhc2giOiJqaFl3c1pyTnZ0dFNYQnR6QVMtWlNnIn0.yJePyxdJWyydG4HM97oQag6ulGKa5Afw-LHYYEXz7lVy8v0IJD0mSO9WtowlWJIeD2Vvthuj71XUfHsgz0LD9rK0VBucJbd_OiIXpbwPUqBcdj82DNLFXDJfCJnUC-Rv8QP7OUVBvLjvBQ6WYMrx1Qnq8xP6qeL_ohKwRmo6EDhZRkYBz9gFhfha1ZlKcnyR73nXdShwy7OmmyiRvVWPBf_GgSsfz8FNNoKySW1MA4tRa7cl3zPlyCnWyLaZ3kcQsmTqarHG--YXSDF5ozZ_Sx6TkunCxrOYzOFNcPyeIWqI84cemM6TgMBw9jhzMCk7Y4Fzxe5KEYJH4GlGA4s4zg
// Header
{
"alg": "PS256",
"typ": "JWT",
"kid": "kid1234"
}
// Payload
{
"iss": "foo.bar.testissuer",
"exp": 1551201068,
"at_hash": "jhYwsZrNvttSXBtzAS-ZSg"
}
I have a working implementation for RS256 encoded JWT which is using the JWTSecurityTokenHandler provided in Microsoft.IdentityModel.Tokens and System.IdentityModel.Tokens.Jwt. For the RS256 implementation I have a IssuerSigningKeyResolver that is making custom checks for the kid and supplying the public key
var tokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = issuer,
ValidateLifetime = true,
RequireSignedTokens = true,
RequireExpirationTime = true,
ValidateAudience = false,
ValidateIssuer = true,
IssuerSigningKeyResolver = (string token, SecurityToken securityToken, string kid, TokenValidationParameters validationParameters) =>
{
// Custom kid checks
var rsa = RSA.Create();
rsa.ImportParameters(new RSAParameters
{
Exponent = Base64UrlEncoder.DecodeBytes(matchingKid.E),
Modulus = Base64UrlEncoder.DecodeBytes(matchingKid.N),
});
latestSecurityKeys.Add(matchingKid.Kid, new RsaSecurityKey(rsa));
var securityKeys = new SecurityKey[1]
{
new RsaSecurityKey(rsa)
};
return securityKeys;
}
};
var tokenHandler = new JwtSecurityTokenHandler();
try
{
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out SecurityToken validatedToken);
return true;
}
catch (SecurityTokenException ex)
{
// Do something with ex
return false;
}
This implementation is not working for PS256 encoded JWT. I debugged the JwtSecurityTokenHandler inside System.IdentityModel.Tokens.Jwt, but it seems that even though PS256 is in the supported algorithms list the verification fails.
I must state again that my knowledge on this subject is limited. From what I understand RSA256 and PS256 are in the same family of algorithms? Would I be better off to just create a custom validation of the PS256 JWT using another library like jose-jwt?
After raising the issue with Microsoft it seems that right now such validation is not supported by Microsoft.IdentityModel.Tokens and System.IdentityModel.Tokens.Jwt . Details can be found here - https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1117
In the end I validated my token using jose-jwt and some custom checks.
private bool IsValid(string token, string issuer, string configId)
{
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
var jwtSecurityToken = jwtSecurityTokenHandler.ReadToken(token) as JwtSecurityToken;
// Extract the kid from token header
var kidHeader = jwtSecurityToken.Header.Where(k => k.Key.ToLower() == "kid")?.FirstOrDefault();
if (kidHeader?.Value == null) ThrowInvalidOperation($"Failed to find matching kid for Issuer: {issuer.ToLower() }");
var kid = kidHeader?.Value as string;
// Extract the expiration time from token payload
var expirationTime = jwtSecurityToken.Payload?.Exp;
if (expirationTime == null) ThrowInvalidOperation($"Failed to find matching expiration time for Issuer: {issuer.ToLower() }");
// Decode to verify signature
var verifiedToken = JWT.Decode(token, GetPublicKey(kid, issuer, providerId));
if (verifiedToken != null)
{
var json = JsonConvert.DeserializeObject<dynamic>(verifiedToken);
return IsValidIssuer(json, issuer) && IsValidExpirationTime(json, expirationTime);
}
else
{
return false;
}
void ThrowInvalidOperation(string msg) => throw new InvalidOperationException(msg);
}
private bool IsValidIssuer(dynamic json, string issuer)
{
if (json != null && issuer != null)
{
if (json["iss"] == issuer)
{
return true;
}
else
{
return false;
}
}
return false;
}
private bool IsValidExpirationTime(dynamic json, int? expTime)
{
if (json != null && expTime != null)
{
if (json["exp"] == expTime)
{
return true;
}
else
{
return false;
}
}
return false;
}
private RSA GetPublicKey(string kid, string validIssuer, string configId)
{
var openIdConfig = openIdConfigurationProvider.GetOpenIdConfiguration(configId);
var matchingKid = openIdConfig?.JsonWebKeySet?.Keys?.FirstOrDefault(x => x.Kid == kid);
if (matchingKid == null)
{
throw new InvalidOperationException($"kid is null");
}
var rsa = RSA.Create();
rsa.ImportParameters(new RSAParameters
{
Exponent = Base64UrlEncoder.DecodeBytes(matchingKid.E),
Modulus = Base64UrlEncoder.DecodeBytes(matchingKid.N),
});
return rsa;
}
I am calling Async method in a non-async method in the below way in WebApi authentication handler. And it hangs when it executes the inner external Async method. I need to set the claims before the subsequent code executes. So I am setting the return value to the Thread.CurrentPrincipal. Please advise.
I have tried the below and none of them worked.
Task.Run(() => Thread.CurrentPrincipal = this.ValidateTokenAsync(accessToken).GetAwaiter().GetResult()); - this works but the subsequent code execution does not wait on this and so the claims are not utilized there.
Thread.CurrentPrincipal = this.ValidateTokenAsync(accessToken).Result;
Task<ClaimsPrincipal> claimsPrincipalTask = this.ValidateTokenAsync(accessToken);
Task.WaitAll(claimsPrincipalTask);
Thread.CurrentPrincipal = Thread.CurrentPrincipal.GetAwaiter().GetResult();
Thread.CurrentPrincipal = this.ValidateTokenAsync(accessToken).GetAwaiter().GetResult();
private async Task<ClaimsPrincipal> ValidateTokenAsync(string accessToken)
{
LoggingUtilities.Logger.TraceInformation("Validating JWT.");
ClaimsPrincipal principal = ClaimsPrincipal.Current;
if (principal == null || principal.Identity == null || !principal.Identity.IsAuthenticated)
{
principal = await JwtValidator.ValidateTokenAsync(accessToken).ConfigureAwait(false);
}
return principal;
}
The JwtValidator.ValidateTokenAsync method:
public static async Task<ClaimsPrincipal> ValidateTokenAsync(string accessToken)
{
string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
string issuer = null;
string stsDiscoveryEndpoint = string.Format(CultureInfo.InvariantCulture, "{0}/.well-known/openid-configuration", authority);
List<SecurityToken> signingTokens = null;
try
{
// The issuer and signingTokens are cached for 24 hours. They are updated if any of the conditions in the if condition is true.
if (DateTime.UtcNow.Subtract(stsMetadataRetrievalTime).TotalHours > 24
|| string.IsNullOrEmpty(globalIssuer)
|| globalSigningTokens == null)
{
// Get tenant information that's used to validate incoming jwt tokens
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync().ConfigureAwait(false);
globalIssuer = config.Issuer;
globalSigningTokens = config.SigningTokens.ToList();
stsMetadataRetrievalTime = DateTime.UtcNow;
}
issuer = globalIssuer;
signingTokens = globalSigningTokens;
}
catch (Exception)
{
LoggingUtilities.Logger.TraceWarning("Failed to get signing tokens.");
throw;
}
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidAudience = audience,
ValidIssuer = issuer,
IssuerSigningTokens = signingTokens,
CertificateValidator = X509CertificateValidator.None
};
ClaimsPrincipal principal;
try
{
// Validate token.
SecurityToken validatedToken;
principal = tokenHandler.ValidateToken(
accessToken,
validationParameters,
out validatedToken);
}
catch (SecurityTokenValidationException)
{
LoggingUtilities.Logger.TraceWarning("Failed to validate the JWT.");
throw;
}
catch (Exception)
{
LoggingUtilities.Logger.TraceWarning("Failed to validate the JWT.");
throw;
}
return principal;
}