Perpetual expiry of claims in SignInWithClaimsAsync - asp.net-core

I am using ASP.NET Core 3.1 with Identity and storing some basic user information like their full name in a claim using the code below (I am aware of checking password and stuff, ignoring it for brevity):
var user = await _userManager.FindByNameAsync(Input.Username);
var claims = new List<Claim>
{
new Claim("UserFullname", user.Fullname, ClaimValueTypes.String)
}
await _signInManager.SignInWithClaimsAsync(user, Input.RememberMe, claims);
I am accessing it in the _Layout.cshtml using the line below:
var userFullname = User.Claims.Single(c => c.Type == "UserFullname").Value;
The problem is, this seems to expire in some time even though the user is still logged in. I want this to be perpetual until the user logs out.
I am sure there has to be some way in startup.cs to control this and as far as possible, I would like to avoid overriding anything.
--EDIT--
As mentioned in the comments for answer by #yinqiu, I tried the cookie authentication scheme using the line below:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
But it did not help either.

I think you can try to override the SignInWithClaimsAsync method.
public override async Task SignInWithClaimsAsync(ApplicationUser user, AuthenticationProperties authenticationProperties, System.Collections.Generic.IEnumerable<System.Security.Claims.Claim> additionalClaims)
{
if (authenticationProperties != null && authenticationProperties.IsPersistent)
{
authenticationProperties.ExpiresUtc = DateTimeOffset.UtcNow.AddYears(1);
}
await base.SignInWithClaimsAsync(user, authenticationProperties, additionalClaims);
}

This is the appropriate solution of your case:
If you are inheriting Identity Classes (IdentityRole,IdentityUser) into your custom classes then you need to use your inherited classes otherwise you use the default Identity Classes. You need a custom ClaimIdentity Class let assume 'ApplicationClaimsIdentityFactory' and this class should be inherited by UserClaimsPrincipalFactory<AspNetUser, AspNetRole>
Step1 Register your dependencies in Startup.cs
services.AddIdentity<AspNetUser, AspNetRole>().AddEntityFrameworkStores<ICRCOMDMSEntities>().AddDefaultTokenProviders();
services.AddScoped<IUserClaimsPrincipalFactory<AspNetUser>, ApplicationClaimsIdentityFactory>();
Step2:
Override the method CreateAsync in your custom claimsIdentityFactory Calss and here you need to create your custom claims and return like
public async override Task<ClaimsPrincipal> CreateAsync(AspNetUser user)
{
var principal = await base.CreateAsync(user);
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim("UserLastLogin", user.LastLoginDate.ToString("dd/MM/yyyy hh:mm:ss tt"))
});
return principal;
}
Now your claims persists until user is logged in.

Related

How to handle array claim values in ASP.net Core using OIDC

Im running Skorubas implementation of IdentityServer4
https://github.com/skoruba/IdentityServer4.Admin
For some reason I end up receiving a single role-claim with the claim-type "role" and a value of an array with all roles for the current user:
["SkorubaIdentityAdminAdministrator","MyRole"]
Now if I would to protect a "page" using the Authorize-attribute:
[Authorize(Role="MyRole")]
This would always end up with an access denied, since ASP.net Core expects multiple claims with the same claim-type, so in this case the claims would be
type | value
role:"SkorubaIdentityAdminAdministrator"
role:"MyRole"
is there any "best practice" to either parse the claims received and reformat them before they are processed by ASP.net core, or to tell the OpenIdConnect extension to handle the array-format as multiple claims?
Generally claims received in JWTs can be arrays or objects as well as simple types. The way to deal with this when using .NET attributes for authorization is via policies.
They are pretty simple and this Curity tutorial has some examples. This code snippet shows that the entire ClaimsPrincipal is available to policies so you could work with array claims easily in your use case:
options.AddPolicy("lowRisk", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(claim =>
claim.Type == "risk" && Int32.Parse(claim.Value) < 50
)
)
);
[HttpGet("lowrisk")]
[Authorize( Policy = "lowRisk")]
public IActionResult LowRisk()
{
return Ok();
}
Turns out that you can create your own ClaimActions, in the example above I had to do the following:
Firts of all.. create a new class:
public class RoleClaimAction : ClaimAction
{
private const string RoleClaimType = "role";
public RoleClaimAction() : base(RoleClaimType, ClaimValueTypes.String)
{
}
public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
{
//Map array of roles to separate role claims
var roles = userData.TryGetStringArray(RoleClaimType)?.ToList();
if (roles!.Any())
{
foreach (var role in roles!)
{
AddRoleClaim(identity, role, issuer);
}
return;
}
//If we only have one role (not an array), add it as a single role claim
var singleRole = userData.TryGetString(RoleClaimType);
if(!string.IsNullOrEmpty(singleRole))
AddRoleClaim(identity, singleRole, issuer);
}
private void AddRoleClaim(ClaimsIdentity identity, string role, string issuer)
{
identity.AddClaim(new Claim(JwtClaimTypes.Role, role, ClaimValueTypes.String, issuer));
}
}
This will simply validate that the user has a claim called roles, and the re-map the array-values to separate role-claims, which then "hooks" into the auth-framework.
To add your ClaimAction, simply add it as the following to your OpenIdConnectOptions:
options.ClaimActions.Add(new RoleClaimAction())
Now Authorize-attributes with roles, and the User.IsInRole(string) should work properly.

How to extend and validate session in ASP.NET Core Identity?

We want to offer the users to manage their login sessions.
This worked so far pretty easy with ASP.NET Core and WITHOUT the Identity Extensions.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1#react-to-back-end-changes
But how can we invoke this validation with ASP.NET Core Identity?
Problem we have:
How do we store login-session-based information like Browser Version, Device Type and User Position? Do we extend any type or what is the idea?
How do we dynamically set the cookie expiration based on a specific user?
How do we invalidate the Cookie from the backend (like the link above shows)?
How do we required additional password-prompts for special functions?
It feels the ASP.NET Core Identity is still not that extensible and flexible :(
Unfortunately, this area of ASP.NET Identity is not very well documented, which I personally see as a risk for such a sensitive area.
After I've been more involved with the source code, the solution seems to be to use the SignIn process of the SignIn Manager.
The basic problem is that it's not that easy to get your custom claims into the ClaimsIdentity of the cookie. There is no method for that.
The values for this must under no circumstances be stored in the claims of the user in the database, as otherwise every login receives these claims - would be bad.
So I created my own method, which first searches for the user in the database and then uses the existing methods of the SignInManager.
After having a ClaimsIdentity created by the SignIn Manager, you can enrich the Identity with your own claims.
For this I save the login session with a Guid in the database and carry the id as a claim in the cookie.
public async Task<SignInResult> SignInUserAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure)
{
DateTimeOffset createdLoginOn = DateTimeOffset.UtcNow;
DateTimeOffset validTo = createdLoginOn.AddSeconds(_userAuthOptions.ExpireTimeSeconds);
// search for user
var user = await _userManager.FindByNameAsync(userName);
if (user is null) { return SignInResult.Failed; }
// CheckPasswordSignInAsync checks if user is allowed to sign in and if user is locked
// also it checks and counts the failed login attempts
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
if (attempt.Succeeded)
{
// TODO: Check 2FA here
// create a unique login entry in the backend
string browserAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"];
Guid loginId = await _eventDispatcher.Send(new AddUserLoginCommand(user.Id, user.UserName, createdLoginOn, validTo, browserAgent));
// Write the login id in the login claim, so we identify the login context
Claim[] customClaims = { new Claim(CustomUserClaims.UserLoginSessionId, loginId.ToString()) };
// Signin User
await SignInWithClaimsAsync(user, isPersistent, customClaims);
return SignInResult.Success;
}
return attempt;
}
With each request I can validate the ClaimsIdentity and search for the login id.
public class CookieSessionValidationHandler : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
ClaimsPrincipal userPrincipal = context.Principal;
if (!userPrincipal.TryGetUserSessionInfo(out int userId, out Guid sessionId))
{
// session format seems to be invalid
context.RejectPrincipal();
}
else
{
IEventDispatcher eventDispatcher = context.HttpContext.RequestServices.GetRequiredService<IEventDispatcher>();
bool succeeded = await eventDispatcher.Send(new UserLoginUpdateLoginSessionCommand(userId, sessionId));
if (!succeeded)
{
// session expired or was killed
context.RejectPrincipal();
}
}
}
}
See also
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1#react-to-back-end-changes

How to perform async ModelState validation with FluentValidation in Web API using .NET Core

This question is a follow up to this post - How to perform async ModelState validation with FluentValidation in Web API?.
I was wondering if FluentValidation has a way to perform async ModelState validation in .net core web api. I have a FluentValidation Validator class which contains async validation methods such as "MustAsync", which means in my business service class I call the validator manually using "ValidateAsync". I also want to use this same validator class to validate the model coming in from the request. I went through the documents and read that the only way to do this is to manually call the "ValidateAsync()" method since the .net pipeline is synchronous. I would rather not manually have to call this method from within my controller, I would prefer to either register it in the startup (have the framework automatically call the the validator on my model) or decorate my request model with the validator.
Has anyone been able to achieve this?
Thanks!
Based on the linked question, I've adapted the code slightly to be compatible with ASP.NET Core (2.2, in my case). In general, this is using the IAsyncActionFilter interface. You can read about it in the official docs.
public class ModelValidationActionFilter : IAsyncActionFilter
{
private readonly IValidatorFactory _validatorFactory;
public ModelValidationActionFilter(IValidatorFactory validatorFactory) => _validatorFactory = validatorFactory;
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var allErrors = new Dictionary<string, object>();
// Short-circuit if there's nothing to validate
if (context.ActionArguments.Count == 0)
{
await next();
return;
}
foreach (var (key, value) in context.ActionArguments)
{
// skip null values
if (value == null)
continue;
var validator = _validatorFactory.GetValidator(value.GetType());
// skip objects with no validators
if (validator == null)
continue;
// validate
var result = await validator.ValidateAsync(value);
// if it's valid, continue
if (result.IsValid) continue;
// if there are errors, copy to the response dictonary
var dict = new Dictionary<string, string>();
foreach (var e in result.Errors)
dict[e.PropertyName] = e.ErrorMessage;
allErrors.Add(key, dict);
}
if (allErrors.Any())
{
// Do anything you want here, if the validation failed.
// For example, you can set context.Result to a new BadRequestResult()
// or implement the Post-Request-Get pattern.
}
else
await next();
}
}
If you want to apply this filter globally, you can add the filter to the AddMvc call in your Startup class. For example:
services.AddMvc(options =>
{
options.Filters.Add<ModelValidationActionFilter>();
// uncomment the following line, if you want to disable the regular validation
// options.ModelValidatorProviders.Clear();
});
I had trouble getting the code in #nachtjasmin's answer to work with newer versions of FluentValidation. Specifically, the trouble is that ValidateAsync now takes an IValidationContext instead of the model being validated, and the context can't be created without knowing the type of the model at compile time.
Eventually I stumbled upon this answer, which points out that the exact type is not important and uses object instead.
So, instead of:
var result = await validator.ValidateAsync(value);
You can use:
var context = new ValidationContext<object>(value);
var result = await validator.ValidateAsync(context);
Based on the answer above by #nachtjasmin, you can add this in two ways,
Using AddMvc
services.AddControllersWithViews(options =>
{
options.Filters.Add<FluentValidationActionFilter>();
});
Using AddControllersWithViews
services.AddControllersWithViews(options =>
{
options.Filters.Add<FluentValidationActionFilter>();
});
If your's is just a Web API and you don't have any Razor pages involved, then you can consider using AddControllersWithViews over AddMvc, as the AddMvc uses the AddControllersWithViews internally and add the services.AddRazorPages() on top of that.
You can see this info here for AddMvc and here for AddControllersWithViews

How to dynamically resolve controller with endpoint routing?

Upgrading to asp.net core 2.2 in my hobby project there is a new routing system I want to migrate to. Previously I implemented a custom IRouter to be able to set the controller for the request dynamically. The incoming request path can be anything. I match the request against a database table containing slugs and it looks up the a matching data container class type for the resolved slug. After that I resolve a controller type that can handle the request and set the RouteData values to the current HttpContext and passing it along to the default implementation for IRouter and everything works ok.
Custom implementaion of IRouter:
public async Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
var page = _pIndex.GetPage(requestPath);
if (page != null)
{
var controllerType = _controllerResolver.GetController(page.PageType);
if (controllerType != null)
{
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
newRouteData.Values["pageType"] = page.PageType;
newRouteData.Values["controller"] = controllerType.Name.Replace("Controller", "");
newRouteData.Values["action"] = "Index";
context.RouteData = newRouteData;
await _defaultRouter.RouteAsync(context);
}
}
}
A controller to handle a specific page type.
public class SomePageController : PageController<PageData>
{
public ActionResult Index(PageData currentPage)
{
return View("Index", currentPage);
}
}
However I got stuck when I'm trying to figure out how I can solve it using the new system. I'm not sure where I'm suppose to extend it for this behavior. I don't want to turn off the endpoint routing feature because I see an opportunity to learn something. I would aso appreciate a code sample if possible.
In ASP.NET 3.0 there is an new dynamic controller routing system. You can implement DynamicRouteValueTransformer.
Documentation is on the way, look at the github issue

Implementing user session in Sencha and SpringBoot

I am trying to make a web app in Sencha Touch with Springboot as my back-end. My app is going to have users and each one of them is going to have their own separate activity. How do I make my app "know" what user is logged in so it can display their specific details? I am a newbie and don't know exactly how this needs to be done, especially on the server side (Springboot). If somebody could throw some light, that would be awesome! Thanks!
Assuming you are planning to use Spring Security, the current-user data can be obtained through its principal. There are a few ways to get the principal. One way is to have a principal parameter in the controller method, and Spring will inject it. Like this:
#RequestMapping(value = "/user", method = RequestMethod.GET)
#ResponseBody
public String currentUserName(Principal principal) {
return principal;
}
Another way would be to have a utility method like this:
public static User getUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
Object principal = auth.getPrincipal();
if (principal instanceof User) {
return (U) principal;
}
}
return null;
}
This can then be called from the controller method.