I have a github project link where the client sends Authorization header to the server containing the JWT token like below. What I can't understand is how on the server side [Authorize(Role.Admin)]can understand the Role.Admin which is application specific enum value (belonging to the account object - details beow).
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add auth header with jwt if account is logged in and request is to the api url
const account = this.accountService.accountValue;
const isLoggedIn = account && account.jwtToken;
const isApiUrl = request.url.startsWith(environment.apiUrl);
if (isLoggedIn && isApiUrl) {
request = request.clone({
setHeaders: { Authorization: `Bearer ${account.jwtToken}` }
});
}
return next.handle(request);
}
On the server side I have middleware class containing the code that decripts the token like this:
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var accountId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
// attach account to context on successful jwt validation
context.Items["Account"] = await dataContext.Accounts.FindAsync(accountId);
}
catch
{
// do nothing if jwt validation fails
// account is not attached to context so request won't have access to secure routes
Console.WriteLine("Failed");
}
So on the server side I have controller that has [Authorize(Role.Admin)] annotation like below which I can't understand - how Authorize can understand the Role:Admin - which is application specific - is it via introspection?
[Authorize(Role.Admin)]
[HttpGet("all-dates")]
public ActionResult<ScheduleDateTimeResponse> GetAllDates()
{
var dates = _accountService.GetAllDates();
return Ok(dates);
}
The project has a custom AuthorizeAttribute for which the Role enumeration is in scope. This differs from the standard attribute from the BCL. This attribute implements IAuthorizationFilter which the asp.net middleware understands must be invoked against this controller action.
Probably the application is referring to some kind of authentication service before the call endpoint. You should be able to see it in the console logs.
Check in solution nugets if there is no some kind of company aut service package
Ofc if you are sure that role is not a part of JWT token.
you can check that with: https://jwt.io/
Sorry...but I cannot add just simply comment yet...
Related
I wanted to ask for advice for a specific approach using ApiKey and JWT token authentication. My .NET 6 application is like a middleware service, which gets a request from other service through HTTP. After that request, I am checking whether it has JWT token in Headers and if it has, I use it to validate the token. After validating it, I need to pass it to my other service class under the same project, there I create a HttpClient and I want to put that JWT token into it's header section. Unfortunately, I am unable to pass it through. I tried creating 'JwtTokenStore' class, store a JWT there and then pass it with dependency injection. I used AddTransient, but then realized that I got a 'null' value in my service, because it creates another instance of that 'IJwtTokenStore'.
I will give you some code snippets:
ApiKeyHandler:
protected override async Task<AuthenticateResult> HandleAuthenticationAsync()
{
if (Request.Headers.TryGetValue(ApiKeyAuthenticationOptions.ApiKeyHeaderName, out var apiKeyHeaderValues))
{
headerKey = apiKeyHeaderValues.ToArray().FirstOrDefault();
if (!string.IsNullOrEmpty(validKey) && !validKey.Equals(headerKey) && !validKey.Equals(uriKey))
{
return AuthenticateResult.NoResult();
}
}
else
{
// check for JWT token
var jwt = Request.Headers["Authorization"].FirstOrDefault(x => x.StartsWith("Bearer "));
if (string.IsNullOrEmpty(jwt))
return AuthenticateResult.Fail("No ApiKey or JWT token present in request headers.");
// validate JWT token
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
};
try
{
var jwtToken = tokenHandler.ReadJwtToken(jwt[7..]);
var expClaim = jwtToken.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Exp)?.Value;
validationParameters.ValidateLifetime = !string.IsNullOrEmpty(expClaim);
tokenHandler.ValidateToken(jwt[7..], validationParameters, out SecurityToken validatedToken);
// Maybe here I should store somewhere my JWT token if it's valid
}
catch
{
return AuthenticateResult.NoResult();
}
}
}
My custom service constructor where I want to add the JWT token if it's valid:
public CustomService(ILogger<CustomService> logger, IConfiguration configuration, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_apiKey = configuration.GetValue<string>("ApiKey");
_httpClient = httpClientFactory?.CreateClient() ?? new HttpClient();
// here I should get that JWT token and check if it's not null, if null
// then I use ApiKey
if (!isApiKey || !string.IsNullOrEmpty(jwt))
{
_httpClient.DefaultRequestHeaders.Add("Authorization", jwt);
}
else //(!string.IsNullOrEmpty(_apiKey))
{
_httpClient.DefaultRequestHeaders.Add("X-Api-Key", _apiKey);
}
}
Can you help me advicing how should I achieve this solution? Don't forget that this middleware application will be getting a lot of requests at the same time, so I need to know which JWT token should I send to my 3rd party service.
All the answers appreciated!
I've tried creating 'JwtTokenStore', put a JWT there and then try to get it in my custom service. After failing (because of AddTransient), I tried creating 'TokenQueue' class with ConcurrentQueue<string> and store the JWT there. But after sending two requests at the same time, in my 3rd party application it only receives the same token.
I saw an answer in this question: Can API Key and JWT Token be used in the same .Net 6 WebAPI but I also stuck at sending that JWT token forward to my custom service.
I also thought about a solution with Dictionary, that I should put token with some user's name or whatsoever, and then getting a token by that. But I am not sure if it's the best solution.
I am porting a legacy ASP.NET MVC application to .NET Core, I am also trying to replace the existing Identity framework to use AWS Cognito User Pools which are handled by the application load balancer.
I have the majority setup and working, when requests hit the ALB and unauthorised user is redirected to the Cognito hosted login page where they can signin. This all works OK but I want to hook up the information in the JWT (passed back from the ALB) to the ASP.NET Identity framework so I can access the populated "User" object.
The response header header containing the JWT is also non-stadard "X-Amzn-Oidc-Data" rather than in the standard Authorization. I have looked at using the Microsoft.AspNetCore.Authentication.JwtBearer but I beleive this expects the standard Authorization header.
I did come across this project https://github.com/awslabs/aws-alb-identity-aspnetcore which appears to be exactly the kind of thing I need but it appears to have been abandoned. Does anyone have any similar experience in acheiving the same thing which they could share?
UPDATE: this is our updated Startup code based on the comment from Mickaƫl:
services.AddAuthentication("Cognito")
.AddJwtBearer("Cognito", options =>
{
options.Events = options.Events ?? new JwtBearerEvents();
options.Events.OnMessageReceived = context =>
{
string amazonOidcDataHeader = context.Request.Headers["X-Amzn-Oidc-Data"];
if (!string.IsNullOrEmpty(amazonOidcDataHeader))
{
context.Token = amazonOidcDataHeader.Trim();
}
return Task.CompletedTask;
};
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
// get JsonWebKeySet from AWS
var json = new WebClient().DownloadString(parameters.ValidIssuer + "/.well-known/jwks.json");
// serialize the result
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
// cast the result to be the type expected by IssuerSigningKeyResolver
return (IEnumerable<SecurityKey>)keys;
},
ValidIssuer = this.Configuration["Authentication:Cognito:ValidIssuer"],
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidAudience = this.Configuration["Authentication:Cognito:ClientId"],
ValidateAudience = true
};
});
The token is now retrieved from the header and pushed into the context.Token property, the Identity User object still contains no claims.
The built-in JWT provider has an extensibility point to source the token from virtually anywhere through the JwtBearerEvents.OnMessageReceived event.
You could do something like this:
services
.AddAuthentication("Cognito")
.AddJwtBearer("Cognito", options =>
{
options.Events ??= new JwtBearerEvents();
options.Events.OnMessageReceived = context =>
{
const string bearerPrefix = "Bearer ";
string amazonOidcDataHeader = context.Request.Headers["X-Amzn-Oidc-Data"];
if (!string.IsNullOrEmpty(amazonOidcDataHeader) && amazonOidcDataHeader.StartsWith(bearerPrefix, StringComparison.OrdinalIgnoreCase))
{
context.Token = amazonOidcDataHeader.Substring(bearerPrefix.Length).Trim();
}
return Task.CompletedTask;
};
});
For reference, see this extract of code on GitHub which shows the implementation of the handler, and how it uses the token assigned during the MessageReceived event if there is one, or tries to extract the one from the standard Authorization header otherwise: https://github.com/dotnet/aspnetcore/blob/20f5f6fbc3db4a66faeb941e56db6ace333c1817/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs#L58-L91
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 = "" });
}
I have implemented the normal login behavior where a JWT token is issued upon successful logon. The API (ASP.Net) should then restrict access to certain Controller actions using the [Authorize] attribute - however I keep on getting a:
401 - Unauthorized response ("Authorization has been denied for this request.")
I am relatively sure I am not configuring the JWT Token authentication correctly - however I have this in my startup, using Microsoft.Owin.Host.SystemWeb:
public void Configuration(IAppBuilder app) {
ConfigureOAuthTokenConsumption(app);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app) {
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions {
AllowedAudiences = new string[] { "*" },
TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidIssuer = "SomeValidIssuer",
ValidateAudience = true,
ValidAudience = "SomeValidAudience",
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("mysupersecretkey"))
}
}
);
}
With a very simple token being passed through to the request via header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MzM1NzY5MDEsImlzcyI6IlNvbWVWYWxpZElzc3VlciIsImF1ZCI6IlNvbWVWYWxpZEF1ZGllbmNlIn0.YfgbVuLgjTagkZTHwXRh9gQxOq5boeMa7TOgq0keKc0
You can use this site to see the contents of the token
I have followed this excellent link on Securing your ASP.NET Core 2.0 API which is working like a charm, but dotNetCore is not an option for the project I am currently working on - however I have followed a similar path in issuing the tokens.
What am I doing wrong, why am I getting a 401 on my request?
My ASP.NET Core REST API uses 2 authentication middleware - JWT to authenticate users (tokens issued by ADFS), and IdentityServer to authenticate calls from other services. The configuration for those middleware looks like this:
JWT
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new X509SecurityKey(federationMetadata.GetSigningCertificate()),
ValidateIssuer = true,
ValidIssuer = federationMetadata.GetIssuerUri().AbsoluteUri,
ValidateAudience = true,
ValidAudience = validAudience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
Identity Server 4 (IDS)
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = dbConfig.IdentityServer.AbsoluteUri,
ScopeName = Configuration.ServiceName,
RequireHttpsMetadata = false
}
I am having problems with this setup:
When a user passes a token from ADFS, the JWT middleware successfully validates the token. The IDS middleware then cannot validate the token and throws an error:
IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey , KeyId: <MyRsaKey>
This is expected. How can I tell the IDS middleware not to authenticate if an earlier middleware has successfully authenticated the request?
I've tried a couple of things. It seems to me that the ideal would be to work out on the IDS OnMessageReceived event that the user has been authenticated, and tell the IDS middleware to do no further checking:
JwtBearerEvents = new JwtBearerEvents()
{
OnMessageReceived = async (context) =>
{
if(UserIsAlreadyAuthenticated)
DoNothingMore();
}
}
How could I test if the user is authenticated?
How do I tell the middleware to accept the authentication and do no more processing?
I have tried trapping the IDS OnAuthenticationFailed event and skipping the middleware:
OnAuthenticationFailed = async (context) =>
{
if (context.Exception is IdentityServerNotAvailableException)
{
context.SkipToNextMiddleware();
}
}
but I then get a scope validation failure:
Scope validation failed. Return 403.
Where is the scope validation happening? Because it seems that any error in the IDS middleware results in a scope validation error, whether I SkipToNextMiddleware or not.
If I remove the IDS middleware, everything works.
Thanks for any help!
EDIT: I have solved the scope validation issue for now. I tried setting the ValidateScope = false option on the IDS middleware, but this causes a null reference exception inside the middleware; but I've found that if I comment out the ScopeName = Configuration.ServiceName option, the scope validation issue is solved (I handle it in a custom handler - not ideal).
Still need to know: Either how to tell the IdentityServer middleware to skip if the token has been authenticated successfully, or to identify successful previous authentication in the OnMessageReceived event so I can call SkipToNextMiddleware() there myself.
Thanks!