How can I implement Claims-Based Authorization with ASP.NET WebAPI without using Roles? - asp.net

I have an ASP.Net WebAPI 2 Application that uses Claims. The claims are stored as two additional columns in a standard Identity2 AspNetUsers table:
CREATE TABLE [dbo].[AspNetUsers] (
[Id] INT IDENTITY (1, 1) NOT NULL,
....
[SubjectId] INT DEFAULT ((0)) NOT NULL,
[LocationId] INT DEFAULT ((0)) NOT NULL,
CONSTRAINT [PK_dbo.AspNetUsers] PRIMARY KEY CLUSTERED ([Id] ASC)
);
I have modified the ApplicationUser class like this:
public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(ApplicationUserManager manager, string authenticationType)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
ClaimsIdentity userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
// Add custom user claims here
userIdentity.AddClaim(new Claim("SubjectId", this.SubjectId.ToString()));
userIdentity.AddClaim(new Claim("LocationId", this.LocationId.ToString()));
return userIdentity;
}
public int SubjectId { get; set; }
public int LocationId { get; set; }
}
In my register method I add in new data for the SubjectId:
var user = new ApplicationUser() {
UserName = model.UserName,
SubjectId = 25,
LocationId = 4
};
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
Can someone help tell me how I can now go about restricting access to a controller based on this SubjectId at the controller level and also at the method level with something similar to this:
[Authorize(SubjectId = "1,25,26")]
[RoutePrefix("api/Content")]
public class ContentController : BaseController
{
[Authorize(LocationId = "4")]
[Route("Get")]
public IQueryable<Content> Get()
{
return db.Contents;
}
[Authorize(SubjectId = "25")]
[Route("Get/{id:int}")]
public async Task<IHttpActionResult> Get(int id)
{
Content content = await db.Contents.FindAsync(id);
if (content == null)
{
return NotFound();
}
return Ok(content);
}
For months now I have been looking for an example but other than some reference to ThinkTexture product and the following link I have found nothing
Update:
#region Assembly System.Web.Http.dll, v5.2.2.0
// C:\Users\Richard\GitHub\abilitest-server\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll
#endregion
using System;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace System.Web.Http
{
// Summary:
// Specifies the authorization filter that verifies the request's System.Security.Principal.IPrincipal.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : AuthorizationFilterAttribute
{
// Summary:
// Initializes a new instance of the System.Web.Http.AuthorizeAttribute class.
public AuthorizeAttribute();
// Summary:
// Gets or sets the authorized roles.
//
// Returns:
// The roles string.
public string Roles { get; set; }
//
// Summary:
// Gets a unique identifier for this attribute.
//
// Returns:
// A unique identifier for this attribute.
public override object TypeId { get; }
//
// Summary:
// Gets or sets the authorized users.
//
// Returns:
// The users string.
public string Users { get; set; }
// Summary:
// Processes requests that fail authorization.
//
// Parameters:
// actionContext:
// The context.
protected virtual void HandleUnauthorizedRequest(HttpActionContext actionContext);
//
// Summary:
// Indicates whether the specified control is authorized.
//
// Parameters:
// actionContext:
// The context.
//
// Returns:
// true if the control is authorized; otherwise, false.
protected virtual bool IsAuthorized(HttpActionContext actionContext);
//
// Summary:
// Calls when an action is being authorized.
//
// Parameters:
// actionContext:
// The context.
//
// Exceptions:
// System.ArgumentNullException:
// The context parameter is null.
public override void OnAuthorization(HttpActionContext actionContext);
}
}

You can achieve that if you override the Authorize attribute. In your case it should be something like this:
public class ClaimsAuthorize : AuthorizeAttribute
{
public string SubjectID { get; set; }
public string LocationID { get; set; }
protected override bool IsAuthorized(HttpActionContext actionContext)
{
ClaimsIdentity claimsIdentity;
var httpContext = HttpContext.Current;
if (!(httpContext.User.Identity is ClaimsIdentity))
{
return false;
}
claimsIdentity = httpContext.User.Identity as ClaimsIdentity;
var subIdClaims = claimsIdentity.FindFirst("SubjectId");
var locIdClaims = claimsIdentity.FindFirst("LocationId");
if (subIdClaims == null || locIdClaims == null)
{
// just extra defense
return false;
}
var userSubId = subIdClaims.Value;
var userLocId = subIdClaims.Value;
// use your desired logic on 'userSubId' and `userLocId', maybe Contains if I get your example right?
if (!this.SubjectID.Contains(userSubId) || !this.LocationID.Contains(userLocId))
{
return false;
}
//Continue with the regular Authorize check
return base.IsAuthorized(actionContext);
}
}
In your controller that you wish to restrict access to, use the ClaimsAuthorize attribute instead of the normal Authorize one:
[ClaimsAuthorize(
SubjectID = "1,2",
LocationID = "5,6,7")]
[RoutePrefix("api/Content")]
public class ContentController : BaseController
{
....
}

Related

Combine [FromBody] with [FromHeader] in WebAPI in .net Core 3.0

we are writing some API which required sessionId in header and some other data in body.
Is it possible to have only one class automatically parsed partially from header and from body?
Something like:
[HttpGet("messages")]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize(Policy = nameof(SessionHeaderKeyHandler))]
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(PendingMessagesData pendingMessagesRequest)
{
some body...
}
with request class like:
public class PendingMessagesData
{
[FromHeader]
public string SessionId { get; set; }
[FromBody]
public string OrderBy { get; set; }
}
I know, it is possible to do this, but it means, that I have to pass SessionId into the other methods as a parameter, instead of pass only one object. And we would have to do that in every API call.
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(
[FromHeader] string sessionId,
[FromBody] PendingMessagesData pendingMessagesRequest)
{
some body...
}
Thank you,
Jakub
we are writing some API which required sessionId in header and some other data in body. Is it possible to have only one class automatically parsed partially from header and from body
Your GetPendingClockInMessages is annotated with a [HttpGet("messages")]. However, a HTTP GET method has no body at all. Also, it can't consume application/json. Please change it to HttpPost("messages")
Typically, SessionId is not passed in header of Session: {SessionId} like other HTTP headers. Session are encrypted via IDataProtector. In other words, you can't get it by Request.Headers["SessionId"].
Apart from the above two facts, you can create a custom model binder to do that.
Since the Session doesn't come from header directly, let's create a custom [FromSession] attribute to replace your [FromHeader]
public class FromSessionAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromSession", "FromSession Binding Source", true, true);
public BindingSource BindingSource { get { return FromSessionAttribute.Instance; } }
}
And since you're consuming application/json, let's create a binder as below:
public class MyModelBinder : IModelBinder
{
private readonly JsonOptions jsonOptions;
public MyModelBinder(IOptions<JsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions.Value;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var pis = type.GetProperties();
var result= Activator.CreateInstance(type);
var body= bindingContext.ActionContext.HttpContext.Request.Body;
var stream = new System.IO.StreamReader(body);
var json = await stream.ReadToEndAsync();
try{
result = JsonSerializer.Deserialize(json, type, this.jsonOptions.JsonSerializerOptions);
} catch(Exception){
// in case we want to pass string directly. if you don't need this feature, remove this branch
if(pis.Count()==2){
var prop = pis
.Where(pi => pi.PropertyType == typeof(string) )
.Where(pi => !pi.GetCustomAttributesData().Any(ca => ca.AttributeType == typeof(FromSessionAttribute)))
.FirstOrDefault();
if(prop != null){
prop.SetValue( result ,json.Trim('"'));
}
} else{
bindingContext.ModelState.AddModelError("", $"cannot deserialize from body");
return;
}
}
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError("sessionId", $"cannot get SessionId From Session");
return;
} else {
var props = pis.Where(pi => {
var attributes = pi.GetCustomAttributesData();
return attributes.Any( ca => ca.AttributeType == typeof(FromSessionAttribute));
});
foreach(var prop in props) {
prop.SetValue(result, sessionId);
}
bindingContext.Result = ModelBindingResult.Success(result);
}
}
}
How to use
Decorate the property with a FromSession to indicate that we want to get the property via HttpContext.Sessino.Id:
public class PendingMessagesData
{
[FromBody]
public string OrderBy { get; set; } // or a complex model: `public MySub Sub{ get; set; }`
[FromSession]
public string SessionId { get; set; }
}
Finally, add a modelbinder on the action method parameter:
[HttpPost("messages")]
[Produces("application/json")]
[Consumes("application/json")]
public async Task<ActionResult> GetPendingClockInMessages([ModelBinder(typeof(MyModelBinder))]PendingMessagesData pendingMessagesRequest)
{
return Json(pendingMessagesRequest);
}
Personally, I would prefer another way, i.e, creating a FromSessionBinderProvider so that I can implement this without too much effort. :
public class FromSessionDataModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError(sessionId, $"cannot get SessionId From Session");
} else {
bindingContext.Result = ModelBindingResult.Success(sessionId);
}
return Task.CompletedTask;
}
}
public class FromSessionBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var hasFromSessionAttribute = context.BindingInfo?.BindingSource == FromSessionAttribute.Instance;
return hasFromSessionAttribute ?
new BinderTypeModelBinder(typeof(FromSessionDataModelBinder)) :
null;
}
}
(if you're able to remove the [ApiController] attribute, this way is more easier).

How to create dynamic role in asp.net mvc5

I want to create a dynamic role in ASP.NET MVC 5. I do not want to create hardcode roles in the authorization attribute .I want to create roles later.it's a test for my recruitment.Do you have sample code or video In this case?
Just in ASP.NET MVC 5.
Thanks in advance for your help
You mean you need dynamic authorization.
In order to do this.
1.You need to add two more tables(Except identity tables).
AppContent (Columns:{Id, Resource, Function,Description})
RoleRights (Columns:{Id, RoleName,AppContentId).
2.Create CustomAuthorizeAttribute
[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class CustomAuthorize : AuthorizeAttribute
{
//Custom named parameters for annotation
public string Source { get; set; }//Controller Name
public string Function { get; set; }//Action Name
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//Is user logged in?
if (httpContext.User.Identity.IsAuthenticated)
{
if ((!string.IsNullOrEmpty(ResourceKey)) && (!string.IsNullOrEmpty(OperationKey)))
{
//There are many ways to store and validate RoleRights
//1.You can store in Database and validate from Database.
//2.You can store in user claim at the time of login and validate from UserClaims.
//3.You can store in session validate from session
//Below I am using database approach.
var loggedInUserRoles = ((ClaimsIdentity) httpContext.User.Identity).Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value);
//logic to check loggedInUserRoles has rights or not from RoleRights table
return db.RoleRights.Any( x=> x.AppContent.Source == Source && x.AppContent.Function == Function && loggedInUserRoles.Contains( x.AppContent.RoleName));
}
}
//Returns true or false, meaning allow or deny. False will call HandleUnauthorizedRequest above
return base.AuthorizeCore(httpContext);
}
//Called when access is denied
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//User isn't logged in
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(filterContext);
return;
}
//User is logged in but has no access
else
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Account", action = "NotAuthorized" })
);
}
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Check for authorization
if (string.IsNullOrEmpty(this.Source) && string.IsNullOrEmpty(this.Function))
{
this.Source = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
this.Function = filterContext.ActionDescriptor.ActionName;
}
base.OnAuthorization(filterContext);
}
}
3. Assign CustomAuthorizeAttribute to the Controller Action
[CustomAuthorize(Source= "Branch", Function = "Index")]
public ActionResult Index()
{
return View(model);
}
[CustomAuthorize(Source = "Branch", Function = "Details")]
public ActionResult Details(long? id)
{
return View(branch);
}
[CustomAuthorize(Source = "Branch", Function = "Create")]
public ActionResult Create()
{
return View();
}
4.Setup all of your application content like Source(Controller) and Function(Action) in AppContent table.
5.Assign AppContents to a role for allowing to role to access this content.
6.Assign User to Role.
7.Run the application and test.

Disable User in ASPNET identity 2.0

I am looking for a way to disable the user instead of deleting them from the system, this is to keep the data integrity of the related data. But seems ASPNET identity only offers Delete Acccount.
There is a new Lockout feature, but it seems to lockout can be controlled to disable user, but only lock the user out after certain number of incorrect password tries.
Any other options?
When you create a site with the Identity bits installed, your site will have a file called "IdentityModels.cs". In this file is a class called ApplicationUser which inherits from IdentityUser.
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit https://devblogs.microsoft.com/aspnet/customizing-profile-information-in-asp-net-identity-in-vs-2013-templates/ to learn more.
public class ApplicationUser : IdentityUser
There is a nice link in the comments there, for ease click here
This tutorial tells you exactly what you need to do to add custom properties for your user.
And actually, don't even bother looking at the tutorial.
add a property to the ApplicationUser class, eg:
public bool? IsEnabled { get; set; }
add a column with the same name on the AspNetUsers table in your DB.
boom, that's it!
Now in your AccountController, you have a Register action as follows:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, IsEnabled = true };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
I've added the IsEnabled = true on the creation of the ApplicationUser object. The value will now be persisted in your new column in the AspNetUsers table.
You would then need to deal with checking for this value as part of the sign in process, by overriding PasswordSignInAsync in ApplicationSignInManager.
I did it as follows:
public override Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool rememberMe, bool shouldLockout)
{
var user = UserManager.FindByEmailAsync(userName).Result;
if ((user.IsEnabled.HasValue && !user.IsEnabled.Value) || !user.IsEnabled.HasValue)
{
return Task.FromResult<SignInStatus>(SignInStatus.LockedOut);
}
return base.PasswordSignInAsync(userName, password, rememberMe, shouldLockout);
}
Your mileage may vary, and you may not want to return that SignInStatus, but you get the idea.
The default LockoutEnabled property for a User is not the property indicating if a user is currently being locked out or not. It's a property indicating if the user should be subject to lockout or not once the AccessFailedCount reaches the MaxFailedAccessAttemptsBeforeLockout value. Even if the user is locked out, its only a temporary measure to bar the user for the duration of LockedoutEnddateUtc property. So, to permanently disable or suspend a user account, you might want to introduce your own flag property.
You don't need to create a custom property. The trick is to set the
LockoutEnabled property on the Identity user AND set the LockoutoutEndDateUtc to a future date from your code to lockout a user. Then, calling the UserManager.IsLockedOutAsync(user.Id) will return false.
Both the LockoutEnabled and LockoutoutEndDateUtc must meet the criteria of true and future date to lockout a user. If, for example, the LockoutoutEndDateUtc value is 2014-01-01 00:00:00.000 and LockoutEnabled is true, calling theUserManager.IsLockedOutAsync(user.Id) will still return true. I can see why Microsoft designed it this way so you can set a time span on how long a user is locked out.
However, I would argue that it should be if LockoutEnabled is true then user should be locked out if LockoutoutEndDateUtc is NULL OR a future date. That way you don't have to worry in your code about setting two properties (LockoutoutEndDateUtc is NULL by default). You could just set LockoutEnabled to true and if LockoutoutEndDateUtc is NULL the user is locked out indefinitely.
You would need to introduce your own flag into a custom IdentityUser-derived class and implement/enforce your own logic about enable/disable and preventing the user from logging in if disabled.
This all I did actually:
var lockoutEndDate = new DateTime(2999,01,01);
UserManager.SetLockoutEnabled(userId,true);
UserManager.SetLockoutEndDate(userId, lockoutEndDate);
Which is basically to enable lock out (if you don't do this by default already, and then set the Lockout End Date to some distant value.
Ozz is correct, however it may be adviseable to look at the base class and see if you can find a method that is checked for all signin angles - I think it might be CanSignIn?
Now that MS is open source you can see their implementation:
https://github.com/aspnet/AspNetCore/blob/master/src/Identity/src/Identity/SignInManager.cs
(Url has changed to:
https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Core/src/SignInManager.cs)
public class CustomSignInManager : SignInManager<ApplicationUser>
{
public CustomSignInManager(UserManager<ApplicationUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<ApplicationUser>> logger,
IAuthenticationSchemeProvider schemes) : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes)
{
}
public override async Task<bool> CanSignInAsync(ApplicationUser user)
{
if (Options.SignIn.RequireConfirmedEmail && !(await UserManager.IsEmailConfirmedAsync(user)))
{
Logger.LogWarning(0, "User {userId} cannot sign in without a confirmed email.", await UserManager.GetUserIdAsync(user));
return false;
}
if (Options.SignIn.RequireConfirmedPhoneNumber && !(await UserManager.IsPhoneNumberConfirmedAsync(user)))
{
Logger.LogWarning(1, "User {userId} cannot sign in without a confirmed phone number.", await UserManager.GetUserIdAsync(user));
return false;
}
if (UserManager.FindByIdAsync(user.Id).Result.IsEnabled == false)
{
Logger.LogWarning(1, "User {userId} cannot sign because it's currently disabled", await UserManager.GetUserIdAsync(user));
return false;
}
return true;
}
}
Also consider overriding PreSignInCheck, which also calls CanSignIn:
protected virtual async Task<SignInResult> PreSignInCheck(TUser user)
{
if (!await CanSignInAsync(user))
{
return SignInResult.NotAllowed;
}
if (await IsLockedOut(user))
{
return await LockedOut(user);
}
return null;
}
You can use these classes... A clean implemantation of ASP.NET Identity...
It's my own code. int is here for primary key if you want different type for primary key you can change it.
IdentityConfig.cs
public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new ApplicationUserStore(context.Get<ApplicationContext>()));
manager.UserValidator = new UserValidator<ApplicationUser, int>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
manager.UserLockoutEnabledByDefault = false;
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser, int>(
dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
public class ApplicationSignInManager : SignInManager<ApplicationUser, int>
{
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) :
base(userManager, authenticationManager) { }
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
}
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
{
return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
}
}
public class ApplicationRoleManager : RoleManager<ApplicationRole, int>
{
public ApplicationRoleManager(IRoleStore<ApplicationRole, int> store)
: base(store)
{
}
}
public class ApplicationRoleStore : RoleStore<ApplicationRole, int, ApplicationUserRole>
{
public ApplicationRoleStore(ApplicationContext db)
: base(db)
{
}
}
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, int,
ApplicationLogin, ApplicationUserRole, ApplicationClaim>
{
public ApplicationUserStore(ApplicationContext db)
: base(db)
{
}
}
IdentityModel.cs
public class ApplicationUser : IdentityUser<int, ApplicationLogin, ApplicationUserRole, ApplicationClaim>
{
//your property
//flag for users state (active, deactive or enabled, disabled)
//set it false to disable users
public bool IsActive { get; set; }
public ApplicationUser()
{
}
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
}
public class ApplicationUserRole : IdentityUserRole<int>
{
}
public class ApplicationLogin : IdentityUserLogin<int>
{
public virtual ApplicationUser User { get; set; }
}
public class ApplicationClaim : IdentityUserClaim<int>
{
public virtual ApplicationUser User { get; set; }
}
public class ApplicationRole : IdentityRole<int, ApplicationUserRole>
{
public ApplicationRole()
{
}
}
public class ApplicationContext : IdentityDbContext<ApplicationUser, ApplicationRole, int, ApplicationLogin, ApplicationUserRole, ApplicationClaim>
{
//web config connectionStringName DefaultConnection change it if required
public ApplicationContext()
: base("DefaultConnection")
{
Database.SetInitializer<ApplicationContext>(new CreateDatabaseIfNotExists<ApplicationContext>());
}
public static ApplicationContext Create()
{
return new ApplicationContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
}
I upvoted Watson, as there is another public method in SignInManager that accepts TUser user instead of string userName. The accepted answer only suggests overriding the method with the username signature. Both should really be overridden, otherwise there is a means of signing in a disabled user. Here are the two methods in the base implementation:
public virtual async Task<SignInResult> PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure)
{
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
{
return SignInResult.Failed;
}
return await PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure);
}
public virtual async Task<SignInResult> PasswordSignInAsync(User user, string password, bool isPersistent, bool lockoutOnFailure)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
return attempt.Succeeded
? await SignInOrTwoFactorAsync(user, isPersistent)
: attempt;
}
Overriding CanSignIn seems like a better solution to me, as it gets called by PreSignInCheck, which is called in CheckPasswordSignInAsync. From what I can tell from the source, overriding CanSignIn should cover all scenarios. Here is a simple implementation that could be used:
public override async Task<bool> CanSignInAsync(User user)
{
var canSignIn = user.IsEnabled;
if (canSignIn) {
canSignIn = await base.CanSignInAsync(user);
}
return canSignIn;
}
In asp.net Core Identity v3, a new way of preventing a user from signing in has been added. Previously you could require that an account has a confirmed email address or phone number, now you can specify .RequireConfirmedAccount. The default implementation of the IUserConfirmation<> service will behave the same as requiring a confirmed email address, provide your own service to define what confirmation means.
public class User : IdentityUser<string>{
public bool IsEnabled { get; set; }
}
public class UserConfirmation : IUserConfirmation<User>
{
public Task<bool> IsConfirmedAsync(UserManager<User> manager, User user) =>
Task.FromResult(user.IsEnabled);
}
services.AddScoped<IUserConfirmation<User>, UserConfirmation>();
services.AddIdentity<User, IdentityRole>(options => {
options.SignIn.RequireConfirmedAccount = true;
} );
You need to implement your own UserStore to remove the identity.
Also this might help you.

a request level singleton object in asp.net

I trying to write a kind of pseudo singleton implementation. I want it to work similar to how HttpContext does work, where I can get an instance to the context doing something as simple as:
var ctx = HttpContext.Current;
So my implementation goes something like this:
public class AppUser
{
public string Username { get; set; }
public string[] Roles { get; set; }
public AppUser()
{
var appuser = HttpContext.Session["AppUser"] as AppUser;
if(appuser == null)
throw new Exception("User session has expired");
Username = appuser.Username;
Roles = appuser.Roles;
}
}
public class WebAppContext
{
const string ContextKey = "WebAppContext";
WebAppContext() { } //empty constructor
public static WebAppContext Current
{
get
{
var ctx = HttpContext.Current.Items[ContextKey] as WebAppContext;
if(ctx == null)
{
try
{
ctx = new WebAppContext() { User = new AppUser() };
}
catch
{
//Redirect for login
}
HttpContext.Current.Items.Add(ContextKey, ctx);
}
return ctx;
}
}
public AppUser User { get; set; }
}
And I try to consume this object as follows:
var appuser = WebAppContext.Current.User;
Now does the above line guarantee I get the user associated with the correct request context; not some other user which is associated with another concurrent http request being processed?
Apart from the fact that I can't understand why would you need to barely copy the user information from the Session container to the Items container, the answer to your question should be - yes, if the Session data is correct then the same data will be available from your static property.
I wrote a blog entry on that once
http://netpl.blogspot.com/2010/12/container-based-pseudosingletons-in.html

How to use new MVC5 Authentication with existing database

I've looked through the current literature but I'm struggling to workout exactly how to make the new IdentityStore system work with your own database.
My database's User table is called tblMember an example class below.
public partial class tblMember
{
public int id { get; set; }
public string membership_id { get; set; }
public string password { get; set; }
....other fields
}
currently users login with the membership_id which is unique and then I use the id throughout the system which is the primary key. I cannot use a username scenario for login as its not unique enough on this system.
With the examples I've seen it looks like the system is designed to me quite malleable, but i cannot currently workout how to get the local login to use my tblmember table to authenticate using membership_id and then I will have access the that users tblMember record from any of the controllers via the User property.
http://blogs.msdn.com/b/webdev/archive/2013/07/03/understanding-owin-forms-authentication-in-mvc-5.aspx
Assuming you are using EF, you should be able to do something like this:
public partial class tblMember : IUserSecret
{
public int id { get; set; }
public string membership_id { get; set; }
public string password { get; set; }
....other fields
/// <summary>
/// Username
/// </summary>
string UserName { get { return membership_id; set { membership_id = value; }
/// <summary>
/// Opaque string to validate the user, i.e. password
/// </summary>
string Secret { get { return password; } set { password = value; } }
}
Basically the local password store is called the IUserSecretStore in the new system. You should be able to plug in your entity type into the AccountController constructor like so assuming you implemented everything correctly:
public AccountController()
{
var db = new IdentityDbContext<User, UserClaim, tblMember, UserLogin, Role, UserRole>();
StoreManager = new IdentityStoreManager(new IdentityStoreContext(db));
}
Note the User property will contain the user's claims, and the NameIdentifier claim will map to the IUser.Id property in the Identity system. That is not directly tied to the IUserSecret which is just a username/secret store. The system models a local password as a local login with providerKey = username, and loginProvider = "Local"
Edit: Adding an example of a Custom User as well
public class CustomUser : User {
public string CustomProperty { get; set; }
}
public class CustomUserContext : IdentityStoreContext {
public CustomUserContext(DbContext db) : base(db) {
Users = new UserStore<CustomUser>(db);
}
}
[TestMethod]
public async Task IdentityStoreManagerWithCustomUserTest() {
var db = new IdentityDbContext<CustomUser, UserClaim, UserSecret, UserLogin, Role, UserRole>();
var manager = new IdentityStoreManager(new CustomUserContext(db));
var user = new CustomUser() { UserName = "Custom", CustomProperty = "Foo" };
string pwd = "password";
UnitTestHelper.IsSuccess(await manager.CreateLocalUserAsync(user, pwd));
Assert.IsTrue(await manager.ValidateLocalLoginAsync(user.UserName, pwd));
CustomUser fetch = await manager.Context.Users.FindAsync(user.Id) as CustomUser;
Assert.IsNotNull(fetch);
Assert.AreEqual("Custom", fetch.UserName);
Assert.AreEqual("Foo", fetch.CustomProperty);
}
EDIT #2: There's also a bug in the implementation of IdentityAuthenticationmanager.GetUserClaims that is casting to User instead of IUser, so custom users that are not extending from User will not work.
Here's the code that you can use to override:
internal const string IdentityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";
internal const string DefaultIdentityProviderClaimValue = "ASP.NET Identity";
/// <summary>
/// Return the claims for a user, which will contain the UserIdClaimType, UserNameClaimType, a claim representing each Role
/// and any claims specified in the UserClaims
/// </summary>
public override async Task<IList<Claim>> GetUserIdentityClaims(string userId, IEnumerable<Claim> claims) {
List<Claim> newClaims = new List<Claim>();
User user = await StoreManager.Context.Users.Find(userId) as IUser;
if (user != null) {
bool foundIdentityProviderClaim = false;
if (claims != null) {
// Strip out any existing name/nameid claims that may have already been set by external identities
foreach (var c in claims) {
if (!foundIdentityProviderClaim && c.Type == IdentityProviderClaimType) {
foundIdentityProviderClaim = true;
}
if (c.Type != ClaimTypes.Name &&
c.Type != ClaimTypes.NameIdentifier) {
newClaims.Add(c);
}
}
}
newClaims.Add(new Claim(UserIdClaimType, userId, ClaimValueTypes.String, ClaimsIssuer));
newClaims.Add(new Claim(UserNameClaimType, user.UserName, ClaimValueTypes.String, ClaimsIssuer));
if (!foundIdentityProviderClaim) {
newClaims.Add(new Claim(IdentityProviderClaimType, DefaultIdentityProviderClaimValue, ClaimValueTypes.String, ClaimsIssuer));
}
var roles = await StoreManager.Context.Roles.GetRolesForUser(userId);
foreach (string role in roles) {
newClaims.Add(new Claim(RoleClaimType, role, ClaimValueTypes.String, ClaimsIssuer));
}
IEnumerable<IUserClaim> userClaims = await StoreManager.Context.UserClaims.GetUserClaims(userId);
foreach (IUserClaim uc in userClaims) {
newClaims.Add(new Claim(uc.ClaimType, uc.ClaimValue, ClaimValueTypes.String, ClaimsIssuer));
}
}
return newClaims;
}

Resources