I am getting following exception.
I have use post method to get token using service principal
Message = "IDX10708: 'System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler' cannot read this string
I am generating access token using ""https://login.microsoftonline.com/{TenantId}/oauth2/token" and trying to validate that
SecurityToken securityToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
IConfigurationManager<OpenIdConnectConfiguration> configurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>(
$"https://login.microsoftonline.com/{tenantId}/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
TokenValidationParameters validationParameters = new TokenValidationParameters()
{
ValidateLifetime = true,
IssuerSigningKeys = openIdConfig.SigningKeys,
ValidateIssuerSigningKey = true,
RequireSignedTokens = true
};
var userClaims = handler.ValidateToken(token, validationParameters, out securityToken);
return userClaims.Claims.Where(claim => claim.Type == "appid").FirstOrDefault().Value;
I am getting exception while validating
Please try to validate the code by putting try/catch block and review the claims value in the debugger e.g. uniquer_name etc
you can follow this thread for more clarity:-
When is JWTSecurityTokenHandler.ValidateToken() actually valid?
Hope it helps.
Related
I'm trying to validate a token that was provided by Azure AD inside of an AWS Lambda Function. At the moment I have a MVC Website that you can authenticate to Azure AD with, which returns a JWT Token. This JWT will be passed up to an AWS API Gateway where a Lambda Authorizer will verify it.
At first I thought the correct method was to pass the JWT back to Azure AD to verify the token. However after reading this, it appears I need to decrypt the token, and validate the issuer and audience. This lead me to this, which does successfully validate the token. However, if i change mySecret to not match the one configured in Azure AD, it still successfully validates?
var authToken = "JWTToken";
string key = "I thought this needed to be the client secret in Azure AD but any string will still pass verification";
string myTenant = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var myAudience = "api://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var myIssuer = string.Format(CultureInfo.InvariantCulture, "https://sts.windows.net/{0}/", myTenant);
var mySecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key));
var stsDiscoveryEndpoint = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", myTenant);
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
var config = await configManager.GetConfigurationAsync();
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidAudience = myAudience,
ValidIssuer = myIssuer,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = false,
IssuerSigningKey = mySecurityKey,
ValidateAudience = true,
ValidateIssuer = true,
};
var validatedToken = (SecurityToken)new JwtSecurityToken();
// Throws an Exception as the token is invalid (expired, invalid-formatted, etc.)
tokenHandler.ValidateToken(authToken, validationParameters, out validatedToken);
I have Azure AD configured to this, where the Client is the MVC website and the Service is the Lambda Auhorizer. TLDR: This is basically two client registrations, where the Lambda Authoizer has an exposed API and the MVC website has a client secret.
Have a taken the right approach? If so, how do I fix the issue i am facing?
Any help would be appreciated
I've used something like this:
string authority = "https://login.microsoftonline.com/<your-tenant-id>/";
string clientId = "<your-client-id>";
IConfigurationManager<OpenIdConnectConfiguration> configurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>($"{authority}.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParams = new TokenValidationParameters
{
ValidAudience = clientId,
IssuerSigningKeys = openIdConfig.SigningKeys
};
It uses the authority to download the valid issuer, signing keys etc.
I am currently using the JwtSecurityToken class in System.IdentityModels.Tokens namespace. I create a token using the following:
DateTime expires = DateTime.UtcNow.AddSeconds(10);
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var genericIdentity = new System.Security.Principal.GenericIdentity(username, "TokenAuth");
ClaimsIdentity identity = new ClaimsIdentity(claims);
string secret = ConfigurationManager.AppSettings["jwtSecret"].ToString();
var securityKey = new InMemorySymmetricSecurityKey(Encoding.Default.GetBytes(secret));
var signingCreds = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.HmacSha256Signature);
var securityToken = handler.CreateToken(
issuer: issuer,
audience: ConfigurationManager.AppSettings["UiUrl"].ToString(),
signingCredentials: signingCreds,
subject: identity,
expires: expires,
notBefore: DateTime.UtcNow
);
return handler.WriteToken(securityToken);
For some reason even though the expires is set to 10 seconds after the current time it doesn't actually throw an exception when the token is being validated until about 5 minutes. After seeing this, I thought maybe there was a minimum expire time of 5 minutes, so I set the expire time to:
DateTime.UtcNow.AddMinutes(5);
Then it expires at 10 minutes, but the exception message says that the expire time is set to what it is supposed to be (5 minutes after the user logs in), and when it shows the current time in the exception it is 5 minutes after the expire time. So, it seems to know when it SHOULD expire, but it doesn't actually throw the exception until 5 minutes after the expire time. Then, since the token seems to be adding 5 minutes to whatever time I set it to expire I set the expire time to:
DateTime.UtcNow.AddMinutes(-5).AddSecond(10);
I tested this and so far it still hasn't expired (After more than ten minutes). Can someone please explain why this is happening and what I am doing wrong? Also, if you see anything else with the code I provided any guidance would be appreciated since I am new to using JWTs and this library.
The problem is related ClockSkew. Normally, the validation libraries (at least the MS one) compensate for clock skew. ClockSkew default value is 5 minutes. See some answer here
You can change ClockSkew in TokenValidationParameters:
var tokenValidationParameters = new TokenValidationParameters
{
//...your setting
// set ClockSkew is zero
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
After reading through #Denis Kucherov's answer, I found out that I could use the same custom validator he posted without using the JwtBearerOptions class which would have required me to add a new library.
Also, Since there are two namespaces which contain a lot of these same classes I will make sure to mention that all of these are using the System.IdentityModels... namespaces. (NOT Microsoft.IdentityModels...)
Below is the code I ended up using:
private bool CustomLifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken tokenToValidate, TokenValidationParameters #param)
{
if (expires != null)
{
return expires > DateTime.UtcNow;
}
return false;
}
private JwtSecurityToken ValidateJwtToken(string tokenString)
{
string secret = ConfigurationManager.AppSettings["jwtSecret"].ToString();
var securityKey = new InMemorySymmetricSecurityKey(Encoding.Default.GetBytes(secret));
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
TokenValidationParameters validation = new TokenValidationParameters()
{
ValidAudience = "MyAudience",
ValidIssuer = "MyIssuer",
ValidateIssuer = true,
ValidateLifetime = true,
LifetimeValidator = CustomLifetimeValidator,
RequireExpirationTime = true,
IssuerSigningKey = securityKey,
ValidateIssuerSigningKey = true,
};
SecurityToken token;
ClaimsPrincipal principal = handler.ValidateToken(tokenString, validation, out token);
return (JwtSecurityToken)token;
}
There are seems to be some issue with LifeTimeValidator. You can just override its logic with a custom delegate. Also, use JwtBearerOptions class to control authentication middleware behavior. For example:
new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidIssuer = _configuration["Tokens:Issuer"],
ValidAudience = _configuration["Tokens:Audience"],
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
LifetimeValidator = LifetimeValidator,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Tokens:Key"]))
}
}
And assign LifetimeValidotor delegate, to provide its own timeout validation logic:
private bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken token, TokenValidationParameters #params)
{
if (expires != null)
{
return expires > DateTime.UtcNow;
}
return false;
}
.NET Core Update
This is handled slightly differently in .NET Core, as the TokenValidationParameters are set in Startup.cs using the ConfigureServices() method and then handled automatically by the middleware.
Also note that the older InMemorySymmetricSecurityKey for signing the secret is now deprecated in favor of SymmetricSecurityKey, which is shown below.
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _config.AuthenticationSettings.TokenAuthority,
ValidAudience = _config.AuthenticationSettings.TokenAuthority,
LifetimeValidator = TokenLifetimeValidator.Validate,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_config.AuthenticationSettings.SecurityKey))
};
});
// ...
}
And so I also made my own version of the token validator in #tkd_aj's answer above and threw it in a static class:
public static class TokenLifetimeValidator
{
public static bool Validate(
DateTime? notBefore,
DateTime? expires,
SecurityToken tokenToValidate,
TokenValidationParameters #param
) {
return (expires != null && expires > DateTime.UtcNow);
}
}
I just implemented a JWT token middleware too and although the examples on the internet use UtcNow, I had to use Now or the expire time is off. When I use Now, expiration is spot on.
Below link give you the exact answer, as by default MS have expire time of 5mins.
So either you have to make it custom or time which you will give in
expires: DateTime.Now.AddSeconds(30)
30seconds in above line will be added in expirey time. So total expire time will be 5mins and 30secs
https://github.com/IdentityServer/IdentityServer3/issues/1251
Hope this will help.
we are using Azure Active Directory for our company. We have an internal web project with Asp.Net Core 3.
We are using a React as a frontend. The user is able to login with his AAD Credentials and the React app gets a token. The Token should be used to access functions in the ASP.Net Core project. Therefore the token should be validated in ASP.Net Core. Here is my problem. I was able to call the token validation but I'm getting different errors.
The ASP.Net app: https://localhost:44350/
The react app: https://localhost:44395/
This is one of the ASP actions:
[HttpGet]
[Route("GetArticles")]
public async Task<JsonResult> GetArticles()
{
if (!Request.Headers.TryGetValue("Authorization", out var authorizationToken))
{
return Json(Unauthorized());
}
if (!authorizationToken.Any())
{
return Json(Unauthorized());
}
var jwt = await Validate(authorizationToken.First());
return Json(await _unitOfWork.ArticlesRepos.GetAllAsync());
}
The token is passed and the validation is triggered. The validation is in the Validate method:
public async Task<JwtSecurityToken> Validate(string token)
{
string tenant = _configuration.GetValue<string>("Tenant");
string publicKey = _configuration.GetValue<string>("AadPubKey");
//string stsDiscoveryEndpoint = $"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
//string stsDiscoveryEndpoint = $"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token";
string stsDiscoveryEndpoint = $"https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
var IssuerSigningKeys = config.SigningKeys;
var x = new X509Certificate2(Encoding.ASCII.GetBytes(publicKey));
var y = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(publicKey));
var rsa = new RSACryptoServiceProvider();
string exponentvalue = "AQAB";
var e = Base64UrlEncoder.DecodeBytes(exponentvalue);
var N = publicKey;
var modulus = Base64UrlEncoder.DecodeBytes(N);
rsa.ImportParameters(
new RSAParameters()
{
Modulus = modulus,
Exponent = e
});
var signingKey = new RsaSecurityKey(rsa);
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = true,
ValidIssuer = stsDiscoveryEndpoint,
//ValidIssuer = "https://localhost:44350/",
//ValidAudience = "https://localhost:44395/",
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateLifetime = false
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
var result = tokendHandler.ValidateToken(token, validationParameters, out SecurityToken jwt);
return jwt as JwtSecurityToken;
}
As you can see, we tried diffferent things. We are getting following errors at the line with
var result = tokendHandler.ValidateToken(token, validationParameters, out SecurityToken jwt);:
When we use IssuerSigningKey = y, =>
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException
HResult=0x80131500
Message = IDX10501: Signature validation failed. Unable to match key:
kid: 'someId'.
Exceptions caught:
'System.NotSupportedException: IDX10634: Unable to create the SignatureProvider.
Algorithm: 'RS256', SecurityKey: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey, KeyId: '', InternalId: ''.'
is not supported
When we use IssuerSigningKey = signingKey, =>
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException
HResult=0x80131500
Message = IDX10501: Signature validation failed. Unable to match key:
kid: 'someId'.
Exceptions caught:
''.
I have no idea how to configure the TokenValidationParameters. When I look in https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys I see the key which is stated in the Exception. But there are numerous informations.
{"keys":
[{"kty":"RSA"
,"use":"sig"
,"kid":"someId"
,"x5t":"someId"
,"n":"longString"
,"e":"AQAB"
,"x5c":["otherLongString"]
,"issuer":"https://login.microsoftonline.com/myTenant/v2.0"},
{...},{...}]
How can we make the validation work?
Thanks in advance
Getting an IDX10634 error from an RS256 algorithm using a SymmetricSecurityKey looks like behavior that has been fixed in newer versions of IdentityModel. Which version of the IdentityModel assemblies are you using? Please try updating to the latest version (currently 6.7.1) and see if your issue persists.
As a few side notes :
JwtSecurityTokenHandler / TokenValidationParameters are meant to be long lived objects. TokenValidationParameters for example, has a cache that can be leveraged when reused.
Is this a SPA app? If it is, this sample should be useful:
https://github.com/Azure-Samples/ms-identity-javascript-react-spa-dotnetcore-webapi-obo
I am trying to validate a valid JWT using this code below but am getting a strange error
"IDX10501: Signature validation failed. Unable to match key:
kid: 'System.String'.
Exceptions caught:
'System.Text.StringBuilder'.
token: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'."
Here is my validation method
ClaimsPrincipal principal = null;
var token = "JWT GOES HERE"
try
{
string sec = "000uVmTXj5EzRjlnqruWF78JQZMT";
var securityKey = new SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(sec));
var now = DateTime.UtcNow;
SecurityToken securityToken;
string tokenIssuer = "https://MyIssuer.com";
TokenValidationParameters validationParameters = new TokenValidationParameters()
{
ValidIssuer = tokenIssuer,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = securityKey
};
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
principal = handler.ValidateToken(token, validationParameters, out securityToken); <---Errors here
}
Here is the value of my JWT. I am using the correct issuer
{
"alg": "RS256",
"kid": "dev",
"x5t": "Sm7aAUSt4Fdv7X1b9jQDf8XwbvQ",
"pi.atm": "xxe8"
}.{
"scope": [],
"client_id": "ClientABC",
"iss": "https://MyIssuer.com",
"jti": "1JLDz",
"sub": "ClientABC",
"exp": 1601609852
}.[Signature]
What am I missing here? Is the the SymmetricSecurityKey since this algorithm is RS256? Am I missing something in my TokenValidationParameter?
Update
After futher investigation I am getting the error of.
IDX10501: Signature validation failed. Unable to match key:
kid: 'dev'.
Exceptions caught:
'System.NotSupportedException: IDX10634: Unable to create the SignatureProvider.
Algorithm: 'RS256', SecurityKey: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey, KeyId: '', InternalId: 'TdfWgWjCVeM60F3C5TOogJuka1aR5FA_xchwhY9MHH4'.'
is not supported. The list of supported algorithms is available here: https://aka.ms/IdentityModel/supported-algorithms
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures, Boolean cacheProvider)
try to use SecurityAlgorithms.HmacSha256
Example when you issue the token:
Users user = _context.Users.FirstOrDefault(c => c.UserName == userName && c.Password == password);
if(user == null)
{
return Unauthorized();
}
Claim[] claims = new Claim[]
{
new Claim("Id", user.Id.ToString()),
new Claim("Name", user.Name),
new Claim("Email", user.Email),
};
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("000uVmTXj5EzRjlnqruWF78JQZMT"));
var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var token = new
JwtSecurityToken(
"MyProject",
"MyClient",
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: signingCredentials);
return Ok(new JwtSecurityTokenHandler().WriteToken(token));
If you are using .net core app, then in Startup.cs, in ConfigureServices method write this code to validate the token:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = "MyClient",
ValidIssuer = "MyProject",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("000uVmTXj5EzRjlnqruWF78JQZMT"))
};
});
Also don't forget to add the following lines to the Configure method in Startup.cs
app.UseAuthentication();
app.UseAuthorization();
The issue is that you are trying to use a symmetric key with an asymmetric algorithm. The RSA algorithm requires both a public and a private key.
Try using a symmetric algorithm instead, like HS256 (HMAC-SHA256).
Having come here looking for answers myself, let me share another scenario which may crop up in a production setting. So we were having this same cryptic authentication error. In our case, the client application was using our development environment's Token service to generate their bearer token and then sending it to production.
I don't know why it didn't outright just deny instead of failing on "Unable to match Key kid", but using the production token service solved it for us. So be sure to verify that first, since it's a relatively quick and simple thing to do.
I am generating a JWT token in my WindowsService using IdentityModel.Tokens.Jwt, like so:
private JwtSecurityToken GetJwtToken()
{
var symmetricSecurityKey = new SymmetricSecurityKey(Convert.FromBase64String(_secretKey));
var signingCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256Signature);
return new JwtSecurityToken(
"myIssuer",
expires: DateTime.Now.AddMinutes(15),
signingCredentials: signingCredentials
);
}
Then, I am writing that token with JwtSecurityTokenHandler and sending it in a request to a WebAPI controller:
//some code...
// _tokenHandler below is a JwtSecurityTokenHandler
_httpClient.DefaultRequestHeaders.Add("jwtToken", _tokenHandler.WriteToken(GetJwtToken()));
HttpResponseMessage response = await _httpClient.GetAsync(url);
//...
And on the API side, I am trying to validate the token:
public bool Authenticate(string token)
{
if (string.IsNullOrEmpty(token))
throw new ArgumentEmptyException(nameof(token));
TokenValidationParameters parameters = new TokenValidationParameters
{
ValidIssuer = "myIssuer",
ValidateIssuer = true,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(SecretKey))
};
try
{
new JwtSecurityTokenHandler().ValidateToken(token, parameters, out SecurityToken validatedToken);
return true;
}
catch (SecurityTokenException)
{
return false;
}
}
}
This throws an error below:
IDX12741: JWT: '[PII is hidden. For more details, see
https://aka.ms/IdentityModel/PII.]' must have three segments (JWS) or
five segments (JWE).'
And an example of a generated token, which actually looks like two tokens sent at once, which is baffling me:
eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Nzk2OTc3NzUsImlzcyI6Im15SXNzdWVyIn0.g9Mw7FijNzAzGofll5E44B8cJtOozln3nUjHKgnkdTs,
eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Nzk2OTc3ODAsImlzcyI6Im15SXNzdWVyIn0.Noc3lC0h_ryH6axlQJ2Kk2a8wcp5eQ0QhBqidfjuujo
Any advice?
The JWT token was generated correctly, the problem was in a shared instance of HttpClient. Each consecutive call added to the DefaultRequestHeaders jwtToken value.
When I added logic to reset the value before adding new token, it worked:
_httpClient.DefaultRequestHeaders.Remove("jwtToken"); // new
_httpClient.DefaultRequestHeaders.Add("jwtToken", _tokenHandler.WriteToken(GetJwtToken()));