Blazor Server cookie validation - asp.net-core

I am building a Blazor Server application, which authenticates to AD and issues a cookie used for authentication and authorization (few AD claims are stored in the cookie).
Is there anything "special" I need to add to ensure the cookie was not tampered?
I've already added this part to handle cookie expiration.
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(15);
options.Cookie.MaxAge = options.ExpireTimeSpan;
options.SlidingExpiration = true;
});
When a user logs in, we are building a ClaimsPrincipal which adds some AD groups as claims:
var claimIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var claimsPrincipal = new ClaimsPrincipal(claimIdentity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, new()
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(15),
RedirectUri = Request.Host.Value
});
For example, some users are getting special privileges based on a claim stored in the cookie, we need to validate if the cookie was tampered or not.

Related

aspnet core 2.2 External Authentication

Created an Authentication Api to handle the auth for several apps. This is a basic auth. username and pw. No OAuth with Google etc. The api gets called with the credentials and it responds with an AthenticationResult. It works correctly except on AuthenticationResult.Success. As I learned I cannot serialize the ClaimsPrincipal. As I am reading it seems the answer it to convert to a token. Is this correct? The AuthenticationResult.Failed serializes w/o issue. What is the best solution here. I will continue to look.
thx for reading
General Steps
That's correct, you'll need to complete the following steps:
Return a token from your authentication API.
Configure your application for JWT Bearer authentication.
Include that token as part of an authorize header on every request to the server.
Require authentication/authorization in your controllers.
There is an excellent ASP.NET Core 2.2 JWT Authentication Tutorial you should check out.
There's too much code involved to post all of it in it's entirety, but here are some key snippets (some code slightly modified for greater clarity out of context from the tutorial):
Some Key Code Snippets
Creating the token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
// 'user' is the model for the authenticated user
// also note that you can include many claims here
// but keep in mind that if the token causes the
// request headers to be too large, some servers
// such as IIS may reject the request.
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
Configuring JWT Authentication (in Startup.cs ConfigureServices method)
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
Don't forget to configure the app to actually use authentication in Startup.cs Configure method:
app.UseAuthentication();

Where to store JWT Token in .net core web api?

I am using web api for accessing data and I want to authenticate and authorize web api.For that I am using JWT token authentication. But I have no idea where should I store access tokens?
What I want to do?
1)After login store the token
2)if user want to access any method of web api, check the token is valid for this user,if valid then give access.
I know two ways
1)using cookies
2)sql server database
which one is the better way to store tokens from above?
Alternatively, if you just wanted to authenticate using JWT the implementation would be slightly different
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var user = context.Principal.Identity.Name;
//Grab the http context user and validate the things you need to
//if you are not satisfied with the validation fail the request using the below commented code
//context.Fail("Unauthorized");
//otherwise succeed the request
return Task.CompletedTask;
}
};
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey("MyVeryStrongKeyHiddenFromAnyone"),
ValidateIssuer = false,
ValidateAudience = false
};
});
still applying use authentication before use MVC.
[Please note these are very simplified examples and you may need to tighten your security more and implement best practices such as using strong keys, loading configs perhaps from the environment etc]
Then the actual authentication action, say perhaps in AuthenticationController would be something like
[Route("api/[controller]")]
[Authorize]
public class AuthenticationController : Controller
{
[HttpPost("authenticate")]
[AllowAnonymous]
public async Task<IActionResult> AuthenticateAsync([FromBody]LoginRequest loginRequest)
{
//LoginRequest may have any number of fields expected .i.e. username and password
//validate user credentials and if they fail return
//return Unauthorized();
var claimsIdentity = new ClaimsIdentity(new Claim[]
{
//add relevant user claims if any
}, "Cookies");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await Request.HttpContext.SignInAsync("Cookies", claimsPrincipal);
return Ok();
}
}
in this instance I'm using cookies so I'm returning an HTTP result with Set Cookie. If I was using JWT, I'd return something like
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]LoginRequest loginRequest)
{
//validate user credentials and if they validation failed return a similar response to below
//return NotFound();
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("MySecurelyInjectedAsymKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
//add my users claims etc
}),
Expires = DateTime.UtcNow.AddDays(1),//configure your token lifespan and needed
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey("MyVerySecureSecreteKey"), SecurityAlgorithms.HmacSha256Signature),
Issuer = "YourOrganizationOrUniqueKey",
IssuedAt = DateTime.UtcNow
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
var cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(4);//you can set this to a suitable timeframe for your situation
cookieOptions.Domain = Request.Host.Value;
cookieOptions.Path = "/";
Response.Cookies.Append("jwt", tokenString, cookieOptions);
return Ok();
}
I'm not familiar with storing your users tokens on your back end app, I'll quickly check how does that work however if you are using dotnet core to authenticate with either cookies or with jwt, from my understanding and experience you need not store anything on your side.
If you are using cookies then you just need to to configure middleware to validate the validity of a cookie if it comes present in the users / consumer's headers and if not available or has expired or can't resolve it, you simply reject the request and the user won't even hit any of your protected Controllers and actions. Here's a very simplified approach with cookies.(I'm still in Development with it and haven't tested in production but it works perfectly fine locally for now using JS client and Postman)
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "yourCookieName";
options.Cookie.SameSite = SameSiteMode.None;//its recommended but you can set it to any of the other 3 depending on your reqirements
options.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents
{
OnRedirectToLogin = redirectContext =>//this will be called if an unauthorized connection comes and you can do something similar to this or more
{
redirectContext.HttpContext.Response.StatusCode = 401;
return Task.CompletedTask;
},
OnValidatePrincipal = context => //if a call comes with a valid cookie, you can use this to do validations. in there you have access to the request and http context so you should have enough to work with
{
var userPrincipal = context.Principal;//I'm not doing anything with this right now but I could for instance validate if the user has the right privileges like claims etc
return Task.CompletedTask;
}
};
});
Obviously this would be placed or called in the ConfigureServices method of your startup to register authentication
and then in your Configure method of your Startup, you'd hookup Authentication like
app.UseAuthentication();
before
app.UseMvc()

Azure AD B2C with Angular4 and WebAPI Core2 token validation issue [duplicate]

This question already has an answer here:
Azure AD B2C error - IDX10501: Signature validation failed
(1 answer)
Closed 5 years ago.
Doesn't seem Azure documentation can give a clear example hot to do it right.
There are Angular4 (WebApp) and WebAPI Core 2.0 back-end.Two application configured in Azure B2C. WebApp has WebAPI app in its API access.
Web app gets redirected to https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize. There, credentials provided and then AAD B2C calls back WebApp page with access_token, token_type, expires_in, id_token url parameters.
Then, WebApp makes a request to a protected endpoint of the back-end with access_token in Authorization header. MessageReceivedAsync is called when request hits the back-end and goes all the way through validating the token.
However, when process exits the method next step it goes into is AuthenticationFailed with error.
"IDX10501: Signature validation failed. Unable to match 'kid': 'Base64_kid',
token: '{"alg":"RS256","typ":"JWT","kid":"Base64_kid"}.{"iss":"number of claims"}'."
My understanding that Audience is the WebAPI application id. I have only a SingIn/Up policy.
What am I missing here to complete jwt manual validation w/o errors? Another question, when claimsPrincipal is created when token validated, how does it go into request context to be able to access protected endpoint?
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.Authority = string.Format("https://login.microsoftonline.com/{0}/v2.0/",
Configuration["Authentication:AzureAd:ida:Tenant"], Configuration["Authentication:AzureAd:ida:Policy"]);
options.Audience = Configuration["Authentication:AzureAd:ida:ClientId"];
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed,
OnMessageReceived = MessageReceivedAsync,
OnChallenge = Challenge,
OnTokenValidated = TokenValidated
};
});
...
}
private Task MessageReceivedAsync(MessageReceivedContext arg)
{
string jwtToken = null;
var aadInstance = Configuration["Authentication:AzureAd:ida:AADInstance"];
var tenant = Configuration["Authentication:AzureAd:ida:Tenant"];
var audience = Configuration["Authentication:AzureAd:ida:Audience"];
var policy = Configuration["Authentication:AzureAd:ida:Policy"];
var authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
string _issuer = string.Empty;
List<SecurityKey> _signingTokens = null;
var authHeader = arg.HttpContext.Request.Headers["Authorization"];
// 7 = (Bearer + " ").Length
var token = authHeader.ToString().Substring(7);
try
{
string stsDiscoveryEndpoint = string.Format("{0}/v2.0/.well-known/openid-configuration?p={1}", authority, policy);
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint,
new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = null;
var openIdConfigTask = Task.Run(async () => {
config = await configManager.GetConfigurationAsync();
});
openIdConfigTask.Wait();
_issuer = config.Issuer;
_signingTokens = config.SigningKeys.ToList();
}
catch(Exception ex)
{
...
}
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidAudience = audience,
ValidIssuer = _issuer,
IssuerSigningKeys = _signingTokens
};
var claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
//Thread.CurrentPrincipal = claimsPrincipal; ?
//var ticket = new AuthenticationTicket(claimsPrincipal, arg.Scheme.Name); ?
//arg.HttpContext.User = claimsPrincipal; ?
return Task.FromResult(0);
}
The options.Audience property is correct (i.e. the application identifier for the Web API application) but the JWT bearer authentication middleware is downloading the wrong signing keys because you don't seem to be setting the options.Authority property to the right value.
It must include the Azure AD B2C policy.
You should be setting it to:
https://login.microsoftonline.com/tfp/{tenant}/{policy}/v2.0/'
such as:
https://login.microsoftonline.com/tfp/{Configuration["Authentication:AzureAd:ida:Tenant"]}/{Configuration["Authentication:AzureAd:ida:Policy"]}/v2.0/.
As result of the token validation, the HttpContext.User object contains the claims from the token, so you can control access for example via scopes.

Asp.net Core Persistent Authentication - Custom Cookie Authentication

I'm trying to get a persistent connection so the users only have to use their password once. I've used this doc: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?tabs=aspnetcore2x but the users still get disconnected after a while.
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
principal,
new AuthenticationProperties
{
IsPersistent = true
});
What can I do to get a really persistent connection ?
The persistence granted by IsPersistent is, according to the docs, only meant to imply that the authentication will persist through browsing sessions (that is, it is kept even when the browser is closed). You need a combination of Persistence and to set an expiration time for the cookie. The expiration of the cookie can be set via the CookieAuthenticationOptions (MSDN), using the ExpireTimeSpan option.
Without persistence, expiration of the authentication can be set using the ExpiresUtc option in AuthenticationOptions,
There are few things you should notice when implement persistent cookie authentication.
Configure sliding expiration for cookie in Startup.cs. It will be clearer if you explicitly set values that you needed and don't use default settings.
private void ConfigureAuthentication(IServiceCollection services)
{
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
// true by default
options.SlidingExpiration = true;
// 14 days by default
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
});
}
When user check flag "Remember Me" configure cookie to persist across browser sessions and set absolute expiration (as long as you want). This settings will override SlidingExpiration and ExpireTimeSpan. In login action:
List<Claim> claims = new List<Claim>();
// Prepare user claims...
var userIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
AuthenticationProperties authenticationProperties = new AuthenticationProperties() { IsPersistent = model.RememberMe };
if (model.RememberMe)
{
// One month for example
authenticationProperties.ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(1);
}
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, authenticationProperties);
Confugure data protection. Remember machineKey in old classic asp.net webforms. Otherwise cookie will be reset after every IIS app pool restart. You should configure data protection before authentication in Startup.cs. To store keys in root folder of your app:
private void ConfigureDataProtection(IServiceCollection services, IWebHostEnvironment environment)
{
var keysDirectoryName = "Keys";
var keysDirectoryPath = Path.Combine(environment.ContentRootPath, keysDirectoryName);
if (!Directory.Exists(keysDirectoryPath))
{
Directory.CreateDirectory(keysDirectoryPath);
}
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keysDirectoryPath))
.SetApplicationName("YourApplicationName");
}
From docs:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-5.0
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-5.0

Why ASP.NET Core adds claims twice into User.Claims property?

I have asp.net core application and the application is using OpenIdConnect authentication using IdentityServer3. When the user is authenticated successfully the application receives proper claims from identity server. I can debug the line TokenValidatedContext.Ticket.Principal.Claims in OnTokenValidatd and make sure application receives all the claims.
Code Snippet
var connectOptions = new OpenIdConnectOptions()
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = authority,
ClientId = clientId,
ResponseType = IdentityConstant.IdTokenClaim,
AuthenticationScheme = IdentityConstant.OpenIdAuthenticationScheme,
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme,
PostLogoutRedirectUri = postlogoutRedirectUri,
CallbackPath = IdentityConstant.CallbackPath,
Events = new OpenIdConnectEvents()
{
OnTokenValidated = async context =>
{
var claims = context.Ticket.Principal.Claims;
await Task.FromResult(0);
}
}
};
below is the quick watch of TokenValidatedContext.Ticket.Principal.Claims in OnTokenValidated handler
However, after successful authentication when I debug User.Cliams in Home controller, I see all the claims are added twice.
Below is the quick watch of User.Claims in Home controller
Why the claims are getting added twice in User.Claims?
Because you set openidconnect's AutomaticAuthenticate to true. If you look user identities you will see there are two identities(One for cookie other for openidconnect authentication). Since User.Claims are sum of these identity claims, you see claims twice. So, removing AutomaticAuthenticate = true, from openidconnect options solves the problem.