How does the Jwt tokens work and Errorcode IDX12709 - c#

I implemented a Github project and don't understand how the user and the key system for Jwt work.
I now have a secret-key which is located in the AppSettings and when the user logs in, the following function is executed:
private string GenerateJwtToken(string username)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.token);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("username", username) }), //<-
Expires = DateTime.UtcNow.AddMinutes(30),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
So if I get it right then I generate here the token for the logged in user but what does the line with the username mean?
After that I store the username and the token inside the sessionStorage and if I trigger some other Controller where the [Authorize] attribute is defined, I add followed Header with the fetch:
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${sessionStorage.getItem("token")}`,
},
Then it first run into this functions:
public async Task Invoke(HttpContext context, IAuthService authService)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (token != null)
attachUserToContext(context, authService, token);
await _next(context);
}
private void attachUserToContext(HttpContext context, IAuthService authService, string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
tokenHandler.ValidateToken(token, new TokenValidationParameters //<- Error
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "username").Value);
context.Items["User"] = "user";
}
catch
{
// do nothing if jwt validation fails
// user is not attached to context so request won't have access to secure routes
}
}
So here it validates that the token from the Header is not null and then it tries to do what!? Also why is again the username used here?
When it run the ValidateToken function it return a error: IDX12709: CanReadToken() returned false. JWT is not well formed: '[PII of type 'System.String' is hidden
https://github.com/cornflourblue/dotnet-5-jwt-authentication-api/tree/279c8058669bbfa59902a4473f62e5371167340c

JWT is split into three parts.
The first one is a header with information like encrypting algorithm.
The second part is the payload, where you actually can find your claims, in your case, it's gonna be a username, exp date,
you can add here whatever you need.
Unique username or id is added here for the server to know to who this token belongs and who is calling the server.
In other case how would you figure out who is calling the server, you would have to save this token in some database or somewhere with the assigned user.
The last part is the signature, this part is verified with your secret key to knowing that it's not some fake token, but actually, a token created by you.
Parts are split by dots.
Hard to say why your token validation fails, at first I would try checking your created token on https://jwt.io and see what's inside.
Maybe you validate token with bearer prefix, as it's written that token is formed badly, so it can be the issue.
One more thing instead of using custom middleware auth, you can use the JWT default authentication scheme
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII
.GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
//...
};
});

Related

Configure JwtBearerOptions IssuerSigningKey based on user

I'm getting started with API Authentication in NET Core utilizing Jwt and as per usual, I got to read a couple of examples and tutorials, and one thing I noticed is that most of them have the SymmetricSecutityKey generation based on either a known string stored somewhere(be it a file or hardcoded) or a randomized output. I managed to get the authentication working, but now I've stuck with the following: How to set up the StartUp.cs configuration so it will validate the IssueSigningKey parameter checks more than one key? Bellow, a snippet of working code:
Authentication Controller
var authClaims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.Ticks.ToString())
};
var ssKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("7S79jvOkEdwoRqHx"));
var securityToken = new JwtSecurityToken(
issuer: _apiSettings.BearerValidIssuer,
audience: _apiSettings.BearerValidAudience,
expires: DateTime.Now.AddHours(6),
claims: authClaims,
signingCredentials: new SigningCredentials(ssKey, SecurityAlgorithms.HmacSha256Signature)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(securityToken),
expiration = securityToken.ValidTo,
});
And the current StartUp.cs Config, regarding the Bearer Token:
StartUp.cs
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = _apiSettings.GetValue(typeof(string), "BearerValidIssuer").ToString(),
ValidateAudience = true,
ValidAudience = _apiSettings.GetValue(typeof(string), "BearerValidAudience").ToString(),
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("7S79jvOkEdwoRqHx")),
};
});
I understand that it would be common practice, at least for the sake of example, to have the string generating the symmetric keys stored somewhere in a file (a .json config file, for example), but I would like to generate it and store them in a database along with user info. That key would be passed to the user at some point and then it would be used to generate access tokens via REST request. Is that achievable? Also adding if that is even practical in terms of security, or I would be fine with "just" storying the string in a file?
Alright, for the sake of leaving a reference to whoever has the same doubts, here's how I approached the problem.
First of all, I had to modify my Authentication controller so the signing credentials of the JwtSecurityToken are created according to a string stored per user in the database. This string is being retrieved from an "Authorization" header of the request, and it is to be kept with the users. This is the part where you prove that "you are you". This Authorization token also has two specific Claims, a Role for "WebApi" access, and the user "uid".
Authorization Controller
public async Task<IActionResult> GetAuthenticationToken([FromBody] JSONBody json)
{
try
{
User user = await _userManager.FindByEmailAsync(json.Email);
if (user != null)
{
if (await _userManager.IsInRoleAsync(user, "WebApi"))
{
if (HttpContext.Request.Headers.TryGetValue("Authorization", out StringValues headerValues))
{
string sskeyString = headerValues.First();
if (user.SymmetricSecurityKeyString == sskeyString)
{
var authClaims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.Ticks.ToString()),
new Claim(ClaimTypes.Role, "WebApi"),
new Claim("uid", user.Id)
};
var ssKey = new SymmetricSecurityKey(Convert.FromBase64String(user.SymmetricSecurityKeyString));
var securityToken = new JwtSecurityToken(
issuer: _apiSettings.BearerValidIssuer,
audience: _apiSettings.BearerValidAudience,
expires: DateTime.Now.AddHours(6),
claims: authClaims,
signingCredentials: new SigningCredentials(ssKey, SecurityAlgorithms.HmacSha256Signature)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(securityToken),
expiration = securityToken.ValidTo,
});
}
else
return Unauthorized(new { Message = "Token de authenticação Inválido." });
}
else
return Unauthorized(new { Message = "Header Authorization não encontrado." });
}
else
return Unauthorized(new { Message = "Este usuário não possui acesso à WebApi. Contate a DescontaNet para solicitar este perfil de acesso." });
}
else
return NotFound(new { Message = $"Usuário não encontrado para este email: {json.Email}" });
}
catch (Exception ex)
{
return StatusCode(500, ex.InnerException);
}
}
After reading more about the AddAuthentication().AddJwtBearer() method, I learned that you can set multiple authentication schemes, each one with its own options. The most important option is the SecurityTokenValidators property, which is an IList<ISecurityTokenValidators>. This is what will validate any tokens sent to the API during Authentication. So StartUp.cs now looks like this:
StartUp.cs
services.AddAuthentication()
.AddJwtBearer("apiToken", options => {
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = _apiSettings.GetValue(typeof(string), "BearerValidIssuer").ToString(),
ValidateAudience = true,
ValidAudience = _apiSettings.GetValue(typeof(string), "BearerValidAudience").ToString(),
ValidateIssuerSigningKey = true,
};
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(new DynamicKeyJwtValidationHandler(Configuration.GetConnectionString("DNDrive")));
});
// There is also a policy to require this specific token validation in certain methods
services.AddAuthorization(options => {
options.AddPolicy("WebApiPolicy", policy => {
policy.RequireRole("WebApi");
policy.RequireAuthenticatedUser();
policy.AddAuthenticationSchemes("apiToken");
});
A minor but important detail: calling services.SecurityTokenValidators.Clear() makes sure any default schemes are removed and only yours is going to be checked during authentication. Speaking of which, I had to look up and make a validator of my own, that looks like this:
DynamicKeyJwtTokenValidationHandler class
public class DynamicKeyJwtValidationHandler : JwtSecurityTokenHandler, ISecurityTokenValidator
{
private DNDriveContext db;
public DynamicKeyJwtValidationHandler(string connectionStr)
{
var optionsBuilder = new DbContextOptionsBuilder<DNDriveContext>();
optionsBuilder.UseSqlServer(connectionStr);
db = new DNDriveContext(optionsBuilder.Options);
}
private SecurityKey GetSSKeyForId(string id)
{
var user = db.User.Where(u => u.Id == id).FirstOrDefault();
if (user == null)
throw new Exception("User Id not found");
return new SymmetricSecurityKey(Convert.FromBase64String(user.SymmetricSecurityKeyString));
}
public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
// Read the token before starting validation
JwtSecurityToken incomingToken = ReadJwtToken(token);
// Extract external system ID from the token
string externalSystemId = incomingToken.Claims.First(claim => claim.Type == "uid").Value;
// Retrieve the Symmetric Security Key String from the database
SecurityKey publicKeyForExternalSystem = GetSSKeyForId(externalSystemId);
// Set up the Security Key for that user
validationParameters.IssuerSigningKey = publicKeyForExternalSystem;
// Framework default validation
return base.ValidateToken(token, validationParameters, out validatedToken);
}
}
The class inherits from JwtSecurityTokenHandler to make the validation and implements the ISecurityTokenValidator interface. In my case, it was important that each user had an individual token to validate against, hence why I access the database and retrieve a SymmetricSecurityKey from a string stored in the database. How do I search for the user in the database? This token has a custom Claim of type "uid". With the SecurityKey it is supposed to validate against, I call the base.ValidateToken() method, passing the incoming token and returning the result.
With this, and with that policy created in StartUp.cs, I can easily validate the tokens for any method under a controller with this tag: [Authorize(Policy = "WebApiPolicy")]. There are probably ways to improve the security, as the end user still has to keep his authentication key secured, but at least I managed to add an extra layer of validation, and the JWT tokens have an expiration date.

Fetch access token from authorization header without bearer prefix

I'm using the Microsoft.AspNetCore.Authentication.JwtBearer and System.IdentityModel.Tokens.Jwt packages for my .NET Core project.
There are some controller endpoints protected by the [Authorize] annotation that have to fetch the access token from the request. Currently I'm fetching the access token in my controller method this way:
string accessTokenWithBearerPrefix = Request.Headers[HeaderNames.Authorization];
string accessTokenWithoutBearerPrefix = accessTokenWithBearerPrefix.Substring("Bearer ".Length);
and I would like to know if there is a better "ready to use" solution for this because using the code above might still lead to errors while taking the substring from the bearer token.
Here is a clever way to get the header without having to go in to the headers dictionary. This will also let the framework parse the token, which is what I believe you are looking for:
[HttpGet, Route("someEndpoint")]
public IActionResult SomeEndpoint([FromHeader] string authorization)
{
if(AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
{
// we have a valid AuthenticationHeaderValue that has the following details:
var scheme = headerValue.Scheme;
var parameter = headerValue.Parameter;
// scheme will be "Bearer"
// parmameter will be the token itself.
}
return Ok();
}
You can also grab the header the old-school way:
[HttpGet, Route("someEndpoint")]
public IActionResult SomeEndpoint()
{
var authorization = Request.Headers[HeaderNames.Authorization];
if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
{
// we have a valid AuthenticationHeaderValue that has the following details:
var scheme = headerValue.Scheme;
var parameter = headerValue.Parameter;
// scheme will be "Bearer"
// parmameter will be the token itself.
}
return Ok();
}
What's nice is AuthenticationHeaderValue.TryParse will cover oddball cases like if there is more than once space between the scheme and the token, or if there are spaces before the scheme, or spaces after the token... and trim it up for you.
Now, those cases should never happen, but... they may, and the execution of accessTokenWithBearerPrefix.Substring("Bearer ".Length); would fail. Which is why I believe you wanted a more concrete way of parsing the token.
You can set SaveToken in Startup.cs to true.
services.AddAuthentication()
.AddJwtBearer(options =>
{
// your other config
options.SaveToken = true;
});
and get access token from HttpContext with GetTokenAsync method.
using Microsoft.AspNetCore.Authentication;
public class SampleController : Controller
{
public void Index()
{
var accessToken = HttpContext.GetTokenAsync("access_token");
}
}
You can use the following code to get security token.
var stream ="[encoded jwt]";
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(stream);
var tokenS = handler.ReadToken(stream) as JwtSecurityToken;
Also, if you want to Ignore JWT Bearer token signature, you can refer to the code as below:
public TokenValidationParameters CreateTokenValidationParameters()
{
var result = new TokenValidationParameters
{
ValidateIssuer = false,
ValidIssuer = ValidIssuer,
ValidateAudience = false,
ValidAudience = ValidAudience,
ValidateIssuerSigningKey = false,
//IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey)),
//comment this and add this line to fool the validation logic
SignatureValidator = delegate(string token, TokenValidationParameters parameters)
{
var jwt = new JwtSecurityToken(token);
return jwt;
},
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
};
result.RequireSignedTokens = false;
return result;
}

Security token from TokenValidatedContext from the OnTokenValidated event listener is missing last string segment

I'm using the Microsoft.AspNetCore.Authentication.JwtBearer and System.IdentityModel.Tokens.Jwt for my .NET Core project.
Whenever I generate a new token I store that to the database. First of all this is how I generate a new token
public string GenerateToken(Dictionary<string, object> payload)
{
DateTime tokenExpiresAt = DateTime.Now.AddMilliseconds(1); // from config
byte[] symmetricKey = Convert.FromBase64String("secret"); // from config
SymmetricSecurityKey symmetricSecurityKey = new SymmetricSecurityKey(symmetricKey);
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Claims = payload,
Expires = tokenExpiresAt,
SigningCredentials = new SigningCredentials(symmetricSecurityKey,
SecurityAlgorithms.HmacSha256Signature)
};
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
string token = tokenHandler.WriteToken(securityToken);
return token;
}
The generated sample token is
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIxIiwibmJmIjoxNTk1NDQ1NjMxLCJleHAiOjE1OTU0NDU2OTEsImlhdCI6MTU5NTQ0NTYzMX0.cWvSpKC_yYao2_ziW_ahjjHpUl2SgUZvCmsjXntxCOI
If a client tries to access a protected endpoint the default configuration will handle the basic validation (configured in the DI setup in the Startup file)
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(jwtBearerOptions =>
{
byte[] symmetricKey = Convert.FromBase64String("secret"); // from config
SymmetricSecurityKey symmetricSecurityKey = new SymmetricSecurityKey(symmetricKey);
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = symmetricSecurityKey,
};
jwtBearerOptions.Events = new JwtBearerEvents()
{
OnTokenValidated = ProcessAfterTokenValidation
};
});
As you can see I added a method to the OnTokenValidated event listener. This method should check, if the token exists in the database.
public async Task ProcessAfterTokenValidation(TokenValidatedContext tokenValidatedContext)
{
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
string token = jwtSecurityTokenHandler.WriteToken(tokenValidatedContext.SecurityToken);
// ... check if token exists in db ...
}
The problem with that method is that the generated token is not the exact token as it is stored in the database. There is missing a last segment. The token variable holds this
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIxIiwibmJmIjoxNTk1NDQ1NjMxLCJleHAiOjE1OTU0NDU2OTEsImlhdCI6MTU5NTQ0NTYzMX0.
and the missing last part would be
cWvSpKC_yYao2_ziW_ahjjHpUl2SgUZvCmsjXntxCOI
Does someone know why the last part of the token is missing?
Thanks for help!
The last part of the token is the signature. The purpose of the OnTokenValidated method is to provide a ClaimsIdentity. The signature is not a component of the claims of the token holder.
If you had a key rotation policy on the issuing side a given user could present identical claims with a different signature before and after a key rotation.
The identity of the user is derived from a combination of the issuer plus any claims that identify the user (e.g. username in the token from your example).
In your case, since you are the issuer, the token minus the signature simply represents proof that the user has successfully authenticated against your token issuing endpoint. The claims within the token should lead to a database record rather than the token itself.

ASP.NET Core 3.1 - How to persist JWT Tokens once Authenticated

I have been trying to get JWT Authentication working and it is not entirely clear how this needs to be done, and what the best ways are to do this in ASP.NET Core 3.1.
I was using Cookie based authentication which I assume is tied to the session id, which is tied to the running server instance. If I want to use multiple servers with different IP addresses and ports, I assume that cookies would no longer work and therefor require something else that can be validated across systems.
I have been following various web examples but it is not clear what to do beyond the point where I have a JWT Token once the user has been "Authenticated" - Logged In. Once users are logged in they can access any part of the system via: html links (the menu).
How do I pass the tokens around with all subsequent requests?
Do I redirect the user to a Welcome page after the user has been authenticated and store the token in the browser sessionStore or localStorage or Cookie? What is the best way to deal with this.
options.success = function (obj) {
sessionStorage.setItem("token", obj.token);
sessionStorage.setItem("userName",$("#userName").val());
}
HTTP HEADERS
Would the Authorization HTTP Header variable work and would this be
sent around in all subsequent requests by the browser, acting as the
HTTP client. How long does this HTTP header last, is it lost once the TCP socket is closed? How do I set this HTTP Header Variable in ASP.NET Core
3.1?
Would the server then use this Header to validate the token, and also
pass it on again for use in subsequent requests?
Currently I have this, which returns the token in the body once the user is authenticated:
var claims = await GetClaims(user);
var token = GenerateSecurityToken(claims);
return Ok(new { Token = token })
AJAX CALLS
I have several forms and several AJAX calls, how do implement this as a manual approach seems rather tedious.
Is there a way to get the JWT token from a hidden form variable similar to the AntiForgery token #Html.AntiForgeryToken()
as used in all my Ajax calls?
jQuery using the hidden form variable:
request = $.ajax({
async: true,
url: url,
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
headers: {
RequestVerificationToken:
$('input:hidden[name="__RequestVerificationToken"]').val()
},
WHAT DO I ADD FOR JWT ?
data: JSON.stringify(data)
}).done(function() {
completion();
}).fail(function() {
// fail
});
HTML FORMS
I have Razor Pages and have some forms which then POST back to the controllers. How do I include the token?
CONTROLLERS
Is there anything else that needs to be performed when using JWT
besides what I have in my Startup.cs? I know I need to deal with Token refreshes but I will leave for a seperate question.
LINKS FROM THE MENU - HTTP GET
I could manipulated the menu / links presented to the user, by adding the token to the end of the URL, but how should this be done?
After quite a bit of reading I found some answers along with a working solution.
HTTP HEADERS
Once you have the token, the token needs to be persisted in order to get access to the system. Using HTTP headers to store the token is not going to persist as the HTTP protocols 1.0 and 1.1 and 1.2 will close the TCP socket at some point along with the state it had, the token. Not ideal for WebClients where you do not control Http connections, but could be used for Mobile development, Android or IOS were you can control the HttpHeaders.
LOCAL STORAGE
You could use the browsers localStorage or sessionStorage, but these have some security risks where JavaScript can read the values - XSS attack.
COOKIES
Another option is to store the token within the Cookie; The cookie will be passed along with every http request, and nothing special on the client side needs to happen regarding this. This method is not prone to XSS attacks. But is prone to CSRF. But again CORS can help with this.
It is also best to set the Cookie to be HttpOnly, this way the cookie will only be delivered over HTTPS. Read more here
Here is my implementation based on an article I found
here
Startup.cs ConfigureServices...
// openssl rand -hex 16 => 32 bytes when read
var jwt_key = Configuration.GetSection("JwtOption:IssuerSigningKey").Value;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwt_key));
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = "some uri",
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = "the web",
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
services.AddSingleton(tokenValidationParameters);
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
int minute = 60;
int hour = minute * 60;
int day = hour * 24;
int week = day * 7;
int year = 365 * day;
options.LoginPath = "/auth/login";
options.AccessDeniedPath = "/auth/accessdenied";
options.Cookie.IsEssential = true;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromSeconds(day/2);
options.Cookie.Name = "access_token";
options.TicketDataFormat = new CustomJwtDataFormat(
SecurityAlgorithms.HmacSha256,
tokenValidationParameters);
});
CustomJwtDataFormat This will be validating our tokens.
public class CustomJwtDataFormat :ISecureDataFormat<AuthenticationTicket>
{
private readonly string algorithm;
private readonly TokenValidationParameters validationParameters;
public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters)
{
this.algorithm = algorithm;
this.validationParameters = validationParameters;
}
public AuthenticationTicket Unprotect(string protectedText)
=> Unprotect(protectedText, null);
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
var handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
try
{
principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null)
{
throw new ArgumentException("Invalid JWT");
}
if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal))
{
throw new ArgumentException($"Algorithm must be '{algorithm}'");
}
// Additional custom validation of JWT claims here (if any)
}
catch (SecurityTokenValidationException e)
{
System.Console.WriteLine(e);
return null;
}
catch (ArgumentException e)
{
System.Console.WriteLine(e);
return null;
}
// Validation passed. Return a valid AuthenticationTicket:
return new AuthenticationTicket(principal, new AuthenticationProperties(), "Cookie");
}
// This ISecureDataFormat implementation is decode-only
public string Protect(AuthenticationTicket data)
{
throw new NotImplementedException();
}
public string Protect(AuthenticationTicket data, string purpose)
{
throw new NotImplementedException();
}
}
LoginController After the username and password is validated, call SignInUser
private string GenerateSecurityToken(List<Claim> claims)
{
var tokenHandler = new JwtSecurityTokenHandler();
var expire = System.DateTime.UtcNow.AddMinutes(userService.GetJwtExpireDate());
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = expire,
SigningCredentials = new SigningCredentials(tokenValidationParameters.IssuerSigningKey, SecurityAlgorithms.HmacSha256Signature),
Audience = tokenValidationParameters.ValidAudience,
Issuer = tokenValidationParameters.ValidIssuer
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
private async Task<List<Claim>> GetClaims(UserModel user) {
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Email),
new Claim(ClaimTypes.Email, user.Email),
};
// add roles
var roleList = await userService.UserRoles(user.Email);
foreach (var role in roleList)
{
var claim = new Claim(ClaimTypes.Role, role.Role);
claims.Add(claim);
}
return claims;
}
private async Task<IActionResult> SignInUser(UserModel user, bool rememberMe)
{
var claims = await GetClaims(user);
var token = GenerateSecurityToken(claims);
// return Ok(new { Token = token });
// HttpContext.Request.Headers.Add("Authorization", $"Bearer {token}");
// HttpContext.Response.Cookies.Append(
HttpContext.Response.Cookies.Append("access_token", token, new CookieOptions { HttpOnly = true, Secure = true });
return RedirectToAction("Index", "Home", new { area = "" });
}

Read and Validate JWT token per request

I have a scenario where there are many separate clients connecting via JWT token.
The client (browser) first needs to login (and is given a JWT token)
The client then needs to retrieve their account information, they do this by sending a request to the server (which includes the JWT token. The server (which has access to the secret) reads the JWT token (securely) and should send back the user information, how do I do this?
p.s. Each client has a different secret
I can do this on a per app basis
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
},
Provider = new CookieOAuthBearerProvider("authCookie")
});
But this method will not work on a per request basis....
This is a snippet from what we're currently using (connecting to AzureAD).
You'll need to implement GetSigningCertificates which returns IEnumerable<X509SecurityToken> to validate the JWT is properly signed.
internal static ClaimsPrincipal GetClaimPrincipalFromToken(string jwtSecurityHeader)
{
var jwtSecurityHandler = new JwtSecurityTokenHandler();
var signingCertificates = GetSigningCertificates(ConfigHelper.FederationMetadataDocument);
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidAudience = ConfigHelper.AppIdURI,
ValidIssuer = ConfigHelper.Issuer,
LifetimeValidator =
(before, expires, token, parameters) =>
{
//Don't allow not-yet-active tokens
if (before.HasValue && before.Value > DateTime.Now)
return false;
//If expiration has a date, add 2 days to it
if (expires.HasValue)
return expires.Value.AddDays(2) > DateTime.Now;
//Otherwise the token is valid
return true;
},
ValidateLifetime = true,
IssuerSigningTokens = signingCertificates,
};
var headerParts = jwtSecurityHeader.Split(' ');
if (headerParts.Length != 2 || headerParts[0] != "Bearer")
throw new AuthorizationException(HttpStatusCode.Forbidden, "Invalid token type");
var jwtSecurityToken = headerParts[1];
SecurityToken jwtToken;
var claimsPrincipal = jwtSecurityHandler.ValidateToken(jwtSecurityToken, tokenValidationParameters, out jwtToken);
return claimsPrincipal;
}
You'll need to tweak it a bit for your application, but this should get you most of the way there. Note that this code is parsing a {HeaderType} {Token} format (for example Bearer {token}). If you're simplying parsing the {Token}, you need to remove the .Split(' ')

Categories

Resources