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.
Related
I have Four tables (Student, AspNetUsers, AspNetUserRoles and AspNetRoles).
I have three roles (user, Teacher, Admin).
The teacher/Admin creates a user account(fields ie Email, phone, address, role) with the user role and this is saved in the student table. The teacher gives an Email to the student.
The student creates an account with an Email(Created by teacher/Admin) and password and this is saved in the AspNetUsers table.
My Question Is: How to assign a role to the student that is given by the Teacher/Admin in AspNetUserRoles table (UserID and UserRoleId). UserId is in AspNetUsers table and UserRoleId is in student table
public class RolesAdminController : Controller
{
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager;
private ApplicationUserManager _userManager;
public RolesAdminController(ApplicationUserManager userManager,
ApplicationRoleManager roleManager, ApplicationSignInManager signInManager)
{
UserManager = userManager;
RoleManager = roleManager;
SignInManager = signInManager;
}
[HttpPost]
public async Task<IActionResult> Register(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if(ModelState.IsValid){
var user = new IdentityUser{Username = Input.Name, Email = Input.Email};
var result = await _UserManager.CreateAsync(user,Input.Password);
return RedirectToAction("Index");
}
}
I have no idea.. What should I add in my code to do it? Assign a role to the student that should be done while the student Register the account.
Need Help
Here is hoping that you have your startup code wired up correctly to include the AddRoleManager method like so
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Then your code is tweeked a bit since it seems as though you are tring to make the call from an api endpoint instead of an actual web page, hence issues such as redirecting to a return url will be handled by the caller when they assess whether the response from the api call they made succeeded or not. Note also that I use IdentityUser instead of ApplicationUser, but you can swap it out if you are using the later. Anyway, the code should give you a gist of how to implement it in your own project
public class RolesAdminController : ControllerBase
{
private UserManager<IdentityUser> userManager;
private readonly RoleManager<IdentityRole> roleManager;
public RolesAdminController(UserManager<IdentityUser> userManager,
RoleManager<IdentityRole> roleManager)
{
this.userManager = userManager;
this.roleManager = roleManager;
}
[HttpPost]
public async Task<IActionResult> Register(InputDto inputDto)
{
inputDto ??= new InputDto(); // if input is null, create new input
List<string> errors = inputDto.Errors();
if (errors.Any())
{
return BadRequest(errors);
}
var user = new IdentityUser(){ UserName = inputDto.Name, Email = inputDto.Email };
var result = await userManager.CreateAsync(user, inputDto.Password);
if (!result.Succeeded)
{
return BadRequest(result.Errors); //You can turn that errors object into a list of string if you like
}
string roleName = "";
switch (inputDto.RoleType)
{
case RoleType.User:
roleName = "user";
break;
case RoleType.Teacher:
roleName = "Teacher";
break;
case RoleType.Admin:
roleName = "Admin";
break;
default:
break;
}
//Checking if role is in not system
if(!(await roleManager.RoleExistsAsync(roleName)))
{
await roleManager.CreateAsync(new IdentityRole(roleName));
}
await userManager.AddToRoleAsync(user, roleName);
return Ok(); //Or you can return Created(uriToTheCreatedResource);
}
}
public class InputDto
{
public string Name { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public RoleType RoleType { get; set; }
public List<string> Errors()
{
List<string> vtr = new List<string>();
if (String.IsNullOrWhiteSpace(Name))
{
vtr.Add("Name Cannot be null");
}
if (String.IsNullOrWhiteSpace(Email))
{
vtr.Add("Email Cannot be null");
}
//Do the rest of your validation
return vtr;
}
}
public enum RoleType
{
User, Teacher, Admin
}
I am fairly new to coding with asp.net so there might be an obvious answere to my question but I haven't found one yet.
So currently I am developing a site for project management and I want the users to get notified when an event happens, eg. they were added to a new project, a project has been updated etc.
For that I have expanded the IdentityUser Model with a new property List
public class CojectUser : IdentityUser
{
public List<Notification> Notifications { get; set; }
}
public class Notification
{
public int NotificationID { get; set; }
public string Message { get; set; }
public bool Seen { get; set; }
}
When an event happens I add them to the user's notification list and update the user via the userManager.
public class EventBroker<T> : IEventBroker<T>
{
private readonly UserManager<CojectUser> userManager;
public EventBroker(UserManager<CojectUser> userMgr, IUserValidator<CojectUser> userValid)
{
userManager = userMgr;
}
public async Task NotifyAsync(Message<T> message, List<UserRole> recipients)
{
foreach (var user in recipients)
{
var cojectUser = await userManager.FindByNameAsync(user.Name);
if (cojectUser != null)
{
if (cojectUser.Notifications == null)
{
cojectUser.Notifications = new List<Notification>();
}
cojectUser.Notifications.Add(new Notification
{
Message = message.Information,
Seen = false
});
IdentityResult result = await userManager.UpdateAsync(cojectUser);
if (!result.Succeeded)
{
throw new UserUpdateFailException();
}
}
}
}
}
}
I am able to save the custom data to the database, but I am unable to load it again from database.
When I want to display the user's notifications userManager retrieves an user object with null as notification list. Even though the data is stored in database.
public async Task<IActionResult> Index()
{
CojectUser user = await userManager.GetUserAsync(User);
if(user.Notifications == null)
{
user.Notifications = new List<Notification>();
}
return View(user);
}
Data in database:
Can anybody tell me what I am doing wrong?
UserManager don't eager load properties by default.
You should use DatabaseContext directly.
var user = _context.Users.Include(c => c.Notifications).Where(u => u.Id == user.Id).ToList();
I am building/learning token based authentication with OWIN and I would like to figure out how to insert additional information when creating a new user. The UserManager accepts IdentityUser, but the CreateAsync method only accepts a user name and passowrd. I would like to add at least the email address. I see that there is a SetEmailAsync method, but that requires a second call. I feel like there should be a single call that allows me to insert other columns, but I am not finding any documentation of how to do this, nor closely related questions in StackOverflow.
Here is the save routine:
public class AuthRepository : IDisposable
{
private readonly AuthContext _context;
private readonly UserManager<IdentityUser> _userManager;
public AuthRepository()
{
_context = new AuthContext();
_userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(_context));
}
public async Task<IdentityUser> FindUserAsync(string userName, string password)
{
IdentityUser user = await _userManager.FindAsync(userName, password);
return user;
}
public async Task<IdentityResult> RegisterUserAsync(UserModel userModel)
{
var user = new IdentityUser
{
UserName = userModel.UserName
};
//save all of this in one call?
var result = await _userManager.CreateAsync(user, userModel.Password);
var result1 = await _userManager.SetEmailAsync(userModel.UserName, userModel.EmailAddress);
return result;
}
public async Task<IdentityUser> FindIdentityUserAsync(string userName, string password)
{
var user = await _userManager.FindAsync(userName, password);
return user;
}
public void Dispose()
{
_context.Dispose();
_userManager.Dispose();
}
}
you can create your own User class by inheriting IdentityUser class.
public class User : IdentityUser
{
public string Email { get; set; }
}
var user = new User
{
UserName = userModel.UserName,
Email = userModel.EmailAddress
};
var result = await _userManager.CreateAsync(user, userModel.Password);
Make sure you are using User Class instead of IdentityUser.
There are many samples online using OWIN/Katana to find users in a database based on ausername/password combination and generate a claims principal, such as...
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
// generate claims here...
That's fine if you're creating a new application and want Entity Framework to do the dirty work. But, I have an eight year old monolithic web site that has just been updated to use claims-based authentication. Our database hit is done manually via DAL/SQL and then the ClaimsIdentity is generated from there.
Some people are suggesting that OWIN is easier to use than our manual approach, but I'd like some input from those that use it.
Is it possible to alter how the UserManager factory finds users based on their credentials? Or, is there another approach that I've missed? All the samples I can find online seem to use a boilerplate approach of letting Entity Framework create the database and manage the searches.
ASP.NET Identity is a little bit overly complex, I would say.
In August 2014 they've announced the new version 2.1 and things have changed again.
First of all let's get rid of EntityFramework:
Uninstall-Package Microsoft.AspNet.Identity.EntityFramework
Now we implement our own definition of User implementing the interface IUser (Microsoft.AspNet.Identity):
public class User: IUser<int>
{
public User()
{
this.Roles = new List<string>();
this.Claims = new List<UserClaim>();
}
public User(string userName)
: this()
{
this.UserName = userName;
}
public User(int id, string userName): this()
{
this.Id = Id;
this.UserName = userName;
}
public int Id { get; set; }
public string UserName { get; set; }
public string PasswordHash { get; set; }
public bool LockoutEnabled { get; set; }
public DateTime? LockoutEndDateUtc { get; set; }
public bool TwoFactorEnabled { get; set; }
public IList<string> Roles { get; private set; }
public IList<UserClaim> Claims { get; private set; }
}
As you can see I have defined the type of my Id (int).
Then you have to define your custom UserManager inheriting from Microsoft.AspNet.Identity.UserManager specifying the your user type and the key type.
public class UserManager : UserManager<User, int>
{
public UserManager(IUserStore<User, int> store): base(store)
{
this.UserLockoutEnabledByDefault = false;
// this.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(10);
// this.MaxFailedAccessAttemptsBeforeLockout = 10;
this.UserValidator = new UserValidator<User, int>(this)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = false
};
// Configure validation logic for passwords
this.PasswordValidator = new PasswordValidator
{
RequiredLength = 4,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false,
};
}
}
I've implemented my validation rules here but you can keep it outside if you prefer.
UserManager needs a UserStore (IUserStore).
You will define your DB logic here. There are a few interfaces to implement. Not all of them are mandatory though.
public class UserStore :
IUserStore<User, int>,
IUserPasswordStore<User, int>,
IUserLockoutStore<User, int>,
IUserTwoFactorStore<User, int>,
IUserRoleStore<User, int>,
IUserClaimStore<User, int>
{
// You can inject connection string or db session
public UserStore()
{
}
}
I haven't included all the methods for each interface. Once you have done that you'll be able to write your new user:
public System.Threading.Tasks.Task CreateAsync(User user)
{
}
fetch it by Id:
public System.Threading.Tasks.Task<User> FindByIdAsync(int userId)
{
}
and so on.
Then you'll need to define your SignInManager inheriting from Microsoft.AspNet.Identity.Owin.SignInManager.
public class SignInManager: SignInManager<User, int>
{
public SignInManager(UserManager userManager, IAuthenticationManager authenticationManager): base(userManager, authenticationManager)
{
}
public override Task SignInAsync(User user, bool isPersistent, bool rememberBrowser)
{
return base.SignInAsync(user, isPersistent, rememberBrowser);
}
}
I've only implemented SignInAsync: it will generates a ClaimsIdentity.
That's pretty much it.
Now in your Startup class you have to tell Owin how to create the UserManager and the SignInManager.
app.CreatePerOwinContext<Custom.Identity.UserManager>(() => new Custom.Identity.UserManager(new Custom.Identity.UserStore()));
// app.CreatePerOwinContext<Custom.Identity.RoleManager>(() => new Custom.Identity.RoleManager(new Custom.Identity.RoleStore()));
app.CreatePerOwinContext<Custom.Identity.SignInService>((options, context) => new Custom.Identity.SignInService(context.GetUserManager<Custom.Identity.UserManager>(), context.Authentication));
I haven't used the factories you will find in the default template cause I wanted to keep things as simple as possible.
And enable your application to use the cookie:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<Custom.Identity.UserManager, Custom.Identity.User, int>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) =>
{
var userIdentity = manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
return (userIdentity);
},
getUserIdCallback: (id) => (Int32.Parse(id.GetUserId()))
)}
});
Now in your account controller - or the controller responsible for the login - you will have to get the UserManager and the SignInManager:
public Custom.Identity.SignInManager SignInManager
{
get
{
return HttpContext.GetOwinContext().Get<Custom.Identity.SignInManager>();
}
}
public Custom.Identity.UserManager UserManager
{
get
{
return HttpContext.GetOwinContext().GetUserManager<Custom.Identity.UserManager>();
}
}
You will use the SignInManager for the login:
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
and the UserManager to create the user, add roles and claims:
if (ModelState.IsValid)
{
var user = new Custom.Identity.User() { UserName = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// await UserManager.AddToRoleAsync(user.Id, "Administrators");
// await UserManager.AddClaimAsync(user.Id, new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Country, "England"));
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
It seems complicate ... and it is ... kind of.
If you want to read more about it there's a good explanation here and here.
If you want to run some code and see how it works, I've put together some code which works with Biggy (as I didn't want to waste to much time defining tables and stuff like that).
If you have the chance to download my code from the github repo, you'll notice that I have created a secondary project (Custom.Identity) where I've kept all my ASP.NET Identity stuff.
The only nuget packages you will need there are:
Microsoft.AspNet.Identity.Core
Microsoft.AspNet.Identity.Owin
Until now I have done all authentication work in my MVC3 app, i.e. validate a member, and create a member, through my MemberRepository class. I would now like to go official, with a custom MembershipProvider. So far I have only gleaned that I really need to override this class's ValidateUser method, and since I am not using a Login control, I'm not even sure I absolutely have to do this.
Overriding methods like GetUser and CreateUser brings uninvited types to my party, like MembershipUser, where I have a finely crafted Member class. Please can someone clear up for me whether or not I really need a custom membership provider, if I'm not going to use any built-in controls or the admin tool, and if I do, should I confine my overrides to the absolutely necessary, which is what?
Here's one I wrote for unit testing. It's about as minimal as can be.
public class MockMembershipProvider : MembershipProvider
{
public IList<MembershipUser> Users { get; private set; }
private string _applicationName;
public override string ApplicationName
{
get
{
return _applicationName;
}
set
{
_applicationName = value;
}
}
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
throw new NotImplementedException();
}
public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
{
throw new NotImplementedException();
}
public override MembershipUser CreateUser(
string username,
string password,
string email,
string passwordQuestion,
string passwordAnswer,
bool isApproved,
object providerUserKey,
out MembershipCreateStatus status)
{
var user = new MembershipUser(ProviderName, username, username, email, passwordQuestion, null, isApproved, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
Users.Add(user);
status = MembershipCreateStatus.Success;
return user;
}
public override bool DeleteUser(string username, bool deleteAllRelatedData)
{
var u = Users.Where(mu => mu.UserName.Equals(username, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
if (u == null) return false;
Users.Remove(u);
return true;
}
public override bool EnablePasswordReset
{
get { return false; }
}
public override bool EnablePasswordRetrieval
{
get { return false; }
}
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
var users = (from u in Users
where u.UserName.Equals(usernameToMatch, StringComparison.OrdinalIgnoreCase)
select u).ToList();
totalRecords = users.Count;
return ToMembershipUserCollection(users);
}
public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
{
var list = Users.Skip(pageIndex * pageSize).Take(pageSize);
totalRecords = list.Count();
var result = new MembershipUserCollection();
foreach (var u in list)
{
result.Add(u);
}
return result;
}
public override int GetNumberOfUsersOnline()
{
return Users.Count();
}
public override string GetPassword(string username, string answer)
{
throw new NotImplementedException();
}
public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
return (from u in Users
where u.ProviderUserKey.ToString() == providerUserKey.ToString()
select u).FirstOrDefault();
}
public override MembershipUser GetUser(string username, bool userIsOnline)
{
return (from u in Users
where u.UserName.Equals(username, StringComparison.OrdinalIgnoreCase)
select u).FirstOrDefault();
}
public override string GetUserNameByEmail(string email)
{
return (from u in Users
where u.Email.Equals(email, StringComparison.OrdinalIgnoreCase)
select u.UserName).FirstOrDefault();
}
public override int MaxInvalidPasswordAttempts
{
get { return 3; }
}
public override int MinRequiredNonAlphanumericCharacters
{
get { return 1; }
}
public override int MinRequiredPasswordLength
{
get { return 6; }
}
public override int PasswordAttemptWindow
{
get { return 10; }
}
public override MembershipPasswordFormat PasswordFormat
{
get { throw new NotImplementedException(); }
}
public override string PasswordStrengthRegularExpression
{
get { return null; }
}
public override string Name
{
get
{
return ProviderName;
}
}
public string ProviderName { get; set; }
public override string ResetPassword(string username, string answer)
{
throw new NotImplementedException();
}
public override bool RequiresQuestionAndAnswer
{
get { return false; }
}
public override bool RequiresUniqueEmail
{
get { return true; }
}
private MembershipUserCollection ToMembershipUserCollection(IEnumerable<MembershipUser> users)
{
var result = new MembershipUserCollection();
foreach (var u in users)
{
result.Add(u);
}
return result;
}
public override bool UnlockUser(string userName)
{
return true;
}
public override void UpdateUser(MembershipUser user)
{
var oldUser = Users.Where(u => u.UserName.Equals(user.UserName, StringComparison.OrdinalIgnoreCase)).Single();
var index = Users.IndexOf(oldUser);
Users[index] = user;
}
public override bool ValidateUser(string username, string password)
{
throw new NotImplementedException();
}
public MockMembershipProvider()
{
this.ProviderName = "MockMembershipProvider";
Users = new List<MembershipUser>();
}
}
public class FakeMembershipProvider : MockMembershipProvider
{
public FakeMembershipProvider(string name)
{
this.ProviderName = name ?? "MockMembershipProvider";
}
public override MembershipUser CreateUser(
string username,
string password,
string email,
string passwordQuestion,
string passwordAnswer,
bool isApproved,
object providerUserKey,
out MembershipCreateStatus status)
{
status = MembershipCreateStatus.ProviderError;
var user = new MockMembershipUser();
user.Password = password;
user.User = username;
user.UserKey = providerUserKey;
Users.Add(user);
status = MembershipCreateStatus.Success;
return user;
}
}
public class MockMembershipUser : MembershipUser
{
public string Password { get; set; }
public string User { get; set; }
public object UserKey { get; set; }
public override string UserName { get { return User; } }
public override string Comment { get; set; }
public override object ProviderUserKey { get { return UserKey; } }
public override string GetPassword()
{
return Password ?? string.Empty;
}
Custom MembershipProvider
It is possible to get some nice security features "for free" if you're using a MembershipProvider: You can set up web.config to redirect every non-authenticated user to a login page, for instance. Or you can set up specific parts of the site to only be visible to users with specific roles. If these features don't make sense for your project, or if you're already implementing their equivalent in another way, there's not much point implementing a custom MembershipProvider.
SqlMembershipProvider
One other possibility you may want to consider is actually switching your own implementation to use the SqlMembershipProvider to handle membership functions.
The SqlMembershipProvider provides a robust, proven platform for the common tasks that are annoying to have to reinvent for every project: account creation, validation, deletion, locking, password resets, basic roles, etc. If you've already done all of this yourself without using the SqlMembershipProvider, there really isn't any point creating one just for the sake of having it. However, you should be careful, because there's a good chance that you've done something wrong in your own implementation. For example,
Are you storing passwords as plain text, or as hashes?
Are you open to Rainbow Table attacks, or are you salting your hashes?
Do you lock people's accounts after you've seen 50 or so invalid password attempts in a row, or do you let hackers just keep pounding away until they've brute-forced their way into someone's account?
The SqlMembershipProvider has already addressed all these issues in an easily configurable manner. You may want to have your own membership interfaces and DTOs simply wrap this default MembershipProvider just so you don't have to worry about these various concerns. That way, most of your code doesn't have to interact with these "uninvited types," but you still get the advantages of a widely-used and proven security framework on the back end.
Do you want to decouple your web application from MembershipRepository?
If so, implement all of the same functionality in a custom MembershipProvider so that your app will only depend on the .NET Membership classes (aside from your web.config).
If not, then don't bother.