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()));
Related
I am trying to use the minimal API in NET 7 and failing to generate JWT tokens.
I have collected the following from several sites and youtube videos. Unfortunately I couldn't find a JWT authentication for NET 7 in any post or video, so I had to combine something from NET 6 (maybe that's where the error comes from?).
to keep from building the config, I used the new function in NET 7:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
in appsettings.json I then added the following:
"Jwt": {
"Key": "gfdsgf789087fgfdsgfds087807dfgfdsfg",
"Issuer": "https://localhost:7285",
"Audience": "https://localhost:7285"
}
Then I created an endpoint to request the token:
app.MapPost("/security/createToken",
[AllowAnonymous] (User user) =>.
{
if (user.UserName == "user" && user.Password == "123")
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.UserName)
};
var token = new JwtSecurityToken
(
issuer: builder.Configuration["Jwt:Issuer"],
audience: builder.Configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddDays(60),
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
SecurityAlgorithms.HmacSha256)
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
return Results.Ok(tokenString);
}
return Results.Unauthorized();
});
now when I run the API then I get the token via Swagger. Then if I want to use it on another endpoint:
app.MapGet("/secret",
[Authorize(AuthenticationSchemes =
JwtBearerDefaults.AuthenticationScheme)] () => $"Hello You. This is asecret!!!");
...then I get the error message in Swagger:
Error: response status is 401
www-authenticate: Bearer error="invalid_token",error_description="The signature key was not found".
When I look at tokens via jwt.io I get "Invalid Signature".
Under PAYLOAD:DATA I see my data but without KEY !!!!
{
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "user",
"nbf": 1670946236,
"exp": 1676130236,
"iss": "https://localhost:7285",
"aud": "https://localhost:7285"
}
As I see, KEY is not inserted in the token!? I assume that I forgot something, but what?
Can anyone see what the error is and why my token is not valid?
Thanks
I have finally deleted the Jwt definition from appsettings.json and filled the options as they were given in NET 6 examples.
builder.Services.AddAuthentication().AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://localhost:7285",
ValidAudience = "https://localhost:7285",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey#345"))
};
});
Now it works.
I can't read token claims from Bearer JWT token.
Login is working, the HTTP request comes with a valid JWT token to the backend.
Login returns the token.
Here is my code on server side:
Program.cs
builder.Services.AddAuthentication(m => {
//m.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
m.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
m.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(conf => {
conf.RequireHttpsMetadata = false;
conf.SaveToken = true;
conf.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = Configuration["JWT-Issuer"],
ValidAudience = Configuration["JWT-Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT-Key"])),
ClockSkew = TimeSpan.Zero,
};
});
When I uncomment this line //m.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; postman says unauthorized. When it is kept commented, authorization succeeds.
Token is generated here.
GenerateToken Method:
private object GenerateJwtToken(string Id, string email, ApplicationUser appUser, string appUserRole, string FirstName)
{
List<Claim> claims = null;
claims = new List<Claim> {
new Claim(JwtRegisteredClaimNames.Email,email),
new Claim(JwtRegisteredClaimNames.Jti, appUser.Id),
new Claim("Role",appUserRole),
new Claim("UserName",appUser.UserName),
new Claim("TEMP", FirstName)
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT-Key"]));
var cred = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expire = DateTime.Now.AddDays(Convert.ToDouble(_configuration["JWT-Expiry"]));
var token = new JwtSecurityToken(
issuer: _configuration["JWT-Issuer"],
audience: _configuration["JWT-Issuer"],
claims: claims,
expires: expire,
signingCredentials: cred
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
When JWT Bearer token is passed to the API call decorated with [Authorize] and tested with debugger it shows that custom claim like TEMP is not present in the User.Claims List.
Reading Claims From Token
string Email = User.Claims.SingleOrDefault(x => x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")).Value;
string FirstName= User.Claims.SingleOrDefault(x => x.Type.Equals("TEMP")).Value;
Here, the email can be read successfully but I can't read the FirstName from the token. In fact User.Claims doesn't have the FirstName claim (I mean all the custom claims leaving the Registered JWT Default ClaimTypes), it only has the default token parameters which are emailaddress, id, role etc.
What should I do?
should I need to create custom authentication scheme for this purpose?
Internally in AddJwtBearer, there are some remapping done by the Token hander, where claims are renamed, like
email -> http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
This mapping can be turned of using:
// Or set this flag to false
.AddJwtBearer(opt =>
{
...
opt.MapInboundClaims = false;
});
or setting:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
The actual mapping can be found here
However, I am not sure why the TEMP claim is not found. What claims do you actually see in the authenticated User in your controller? Some claims in some cases are also ignored.
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 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.
There are millions of guides out there, and none of them seem to do what I need. I am creating an Authentication Server, that simply just needs to issue, and validate/reissue tokens. So I can't create a middleware class to "VALIDATE" the cookie or header. I am simply receiving a POST of the string, and I need to validate the token that way, instead of the Authorize middleware that .net core provides.
My Startup Consists of the only Token Issuer Example I could get working.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
var secretKey = "mysupersecret_secretkey!123";
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
var options = new TokenProviderOptions
{
// The signing key must match!
Audience = "AllApplications",
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
Issuer = "Authentication"
};
app.UseMiddleware<TokenProviderMiddleware>(Microsoft.Extensions.Options.Options.Create(options));
I can use the middleware on creation since I just need to intercept the body for the username and password. The middleware takes in the options from the previous Startup.cs code, checks the Request Path and will Generate the token from the context seen below.
private async Task GenerateToken(HttpContext context)
{
CredentialUser usr = new CredentialUser();
using (var bodyReader = new StreamReader(context.Request.Body))
{
string body = await bodyReader.ReadToEndAsync();
usr = JsonConvert.DeserializeObject<CredentialUser>(body);
}
///get user from Credentials put it in user variable. If null send bad request
var now = DateTime.UtcNow;
// Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
// You can add other claims here, if you want:
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, JsonConvert.SerializeObject(user)),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, now.ToString(), ClaimValueTypes.Integer64)
};
// Create the JWT and write it to a string
var jwt = new JwtSecurityToken(
issuer: _options.Issuer,
audience: _options.Audience,
claims: claims,
notBefore: now,
expires: now.Add(_options.Expiration),
signingCredentials: _options.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
///fill response with jwt
}
This large block of code above will Deserialize the CredentialUser json and then execute a stored procedure that returns the User Object. I will then add three claims, and ship it back.
I am able to successfully generate a jwt, and using an online tool like jwt.io, I put the secret key, and the tool says it is valid, with an object that I could use
{
"sub": " {User_Object_Here} ",
"jti": "96914b3b-74e2-4a68-a248-989f7d126bb1",
"iat": "6/28/2017 4:48:15 PM",
"nbf": 1498668495,
"exp": 1498668795,
"iss": "Authentication",
"aud": "AllApplications"
}
The problem I'm having is understanding how to manually check the claims against the signature. Since this is a server that issues and validates tokens. Setting up the Authorize middleware is not an option, like most guides have. Below I am attempting to Validate the Token.
[Route("api/[controller]")]
public class ValidateController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Validate(string token)
{
var validationParameters = new TokenProviderOptions()
{
Audience = "AllMyApplications",
SigningCredentials = new
SigningCredentials("mysupersecret_secretkey!123",
SecurityAlgorithms.HmacSha256),
Issuer = "Authentication"
};
var decodedJwt = new JwtSecurityTokenHandler().ReadJwtToken(token);
var valid = new JwtSecurityTokenHandler().ValidateToken(token, //The problem is here
/// I need to be able to pass in the .net TokenValidParameters, even though
/// I have a unique jwt that is TokenProviderOptions. I also don't know how to get my user object out of my claims
}
}
I stole borrowed this code mostly from the ASP.Net Core source code: https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L45
From that code I created this function:
private string Authenticate(string token) {
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
List<Exception> validationFailures = null;
SecurityToken validatedToken;
var validator = new JwtSecurityTokenHandler();
// These need to match the values used to generate the token
TokenValidationParameters validationParameters = new TokenValidationParameters();
validationParameters.ValidIssuer = "http://localhost:5000";
validationParameters.ValidAudience = "http://localhost:5000";
validationParameters.IssuerSigningKey = key;
validationParameters.ValidateIssuerSigningKey = true;
validationParameters.ValidateAudience = true;
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
// This line throws if invalid
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
// If we got here then the token is valid
if (principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
return principal.Claims.Where(c => c.Type == ClaimTypes.Email).First().Value;
}
}
catch (Exception e)
{
_logger.LogError(null, e);
}
}
return String.Empty;
}
The validationParameters need to match those in your GenerateToken function and then it should validate just fine.