Customising the OWIN/Katana UserManager factory behaviour - asp.net

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

Related

Store additional info when creating new user in IdentityStore in web api

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.

ASP.NET Core 2.0 Bearer Auth without Identity

I thought I had a pretty simple goal in mind when I set out a day ago to implement a self-contained bearer auth webapi on .NET core 2.0, but I have yet to get anything remotely working. Here's a list of what I'm trying to do:
Implement a bearer token protected webapi
Issue tokens & refresh tokens from an endpoint in the same project
Use the [Authorize] attribute to control access to api surface
Not use ASP.Net Identity (I have much lighter weight user/membership reqs)
I'm totally fine with building identity/claims/principal in login and adding that to request context, but I've not seen a single example on how to issue and consume auth/refresh tokens in a Core 2.0 webapi without Identity. I've seen the 1.x MSDN example of cookies without Identity, but that didn't get me far enough in understanding to meet the requirements above.
I feel like this might be a common scenario and it shouldn't be this hard (maybe it's not, maybe just lack of documentation/examples?). As far as I can tell, IdentityServer4 is not compatible with Core 2.0 Auth, opendiddict seems to require Identity. I also don't want to host the token endpoint in a separate process, but within the same webapi instance.
Can anyone point me to a concrete example, or at least give some guidance as to what best steps/options are?
Did an edit to make it compatible with ASP.NET Core 2.0.
Firstly, some Nuget packages:
Microsoft.AspNetCore.Authentication.JwtBearer
Microsoft.AspNetCore.Identity
System.IdentityModel.Tokens.Jwt
System.Security.Cryptography.Csp
Then some basic data transfer objects.
// Presumably you will have an equivalent user account class with a user name.
public class User
{
public string UserName { get; set; }
}
public class JsonWebToken
{
public string access_token { get; set; }
public string token_type { get; set; } = "bearer";
public int expires_in { get; set; }
public string refresh_token { get; set; }
}
Getting into the proper functionality, you'll need a login/token web method to actually send the authorization token to the user.
[Route("api/token")]
public class TokenController : Controller
{
private ITokenProvider _tokenProvider;
public TokenController(ITokenProvider tokenProvider) // We'll create this later, don't worry.
{
_tokenProvider = tokenProvider;
}
public JsonWebToken Get([FromQuery] string grant_type, [FromQuery] string username, [FromQuery] string password, [FromQuery] string refresh_token)
{
// Authenticate depending on the grant type.
User user = grant_type == "refresh_token" ? GetUserByToken(refresh_token) : GetUserByCredentials(username, password);
if (user == null)
throw new UnauthorizedAccessException("No!");
int ageInMinutes = 20; // However long you want...
DateTime expiry = DateTime.UtcNow.AddMinutes(ageInMinutes);
var token = new JsonWebToken {
access_token = _tokenProvider.CreateToken(user, expiry),
expires_in = ageInMinutes * 60
};
if (grant_type != "refresh_token")
token.refresh_token = GenerateRefreshToken(user);
return token;
}
private User GetUserByToken(string refreshToken)
{
// TODO: Check token against your database.
if (refreshToken == "test")
return new User { UserName = "test" };
return null;
}
private User GetUserByCredentials(string username, string password)
{
// TODO: Check username/password against your database.
if (username == password)
return new User { UserName = username };
return null;
}
private string GenerateRefreshToken(User user)
{
// TODO: Create and persist a refresh token.
return "test";
}
}
You probably noticed the token creation is still just "magic" passed through by some imaginary ITokenProvider. Define the token provider interface.
public interface ITokenProvider
{
string CreateToken(User user, DateTime expiry);
// TokenValidationParameters is from Microsoft.IdentityModel.Tokens
TokenValidationParameters GetValidationParameters();
}
I implemented the token creation with an RSA security key on a JWT. So...
public class RsaJwtTokenProvider : ITokenProvider
{
private RsaSecurityKey _key;
private string _algorithm;
private string _issuer;
private string _audience;
public RsaJwtTokenProvider(string issuer, string audience, string keyName)
{
var parameters = new CspParameters { KeyContainerName = keyName };
var provider = new RSACryptoServiceProvider(2048, parameters);
_key = new RsaSecurityKey(provider);
_algorithm = SecurityAlgorithms.RsaSha256Signature;
_issuer = issuer;
_audience = audience;
}
public string CreateToken(User user, DateTime expiry)
{
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user.UserName, "jwt"));
// TODO: Add whatever claims the user may have...
SecurityToken token = tokenHandler.CreateJwtSecurityToken(new SecurityTokenDescriptor
{
Audience = _audience,
Issuer = _issuer,
SigningCredentials = new SigningCredentials(_key, _algorithm),
Expires = expiry.ToUniversalTime(),
Subject = identity
});
return tokenHandler.WriteToken(token);
}
public TokenValidationParameters GetValidationParameters()
{
return new TokenValidationParameters
{
IssuerSigningKey = _key,
ValidAudience = _audience,
ValidIssuer = _issuer,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(0) // Identity and resource servers are the same.
};
}
}
So you're now generating tokens. Time to actually validate them and wire it up. Go to your Startup.cs.
In ConfigureServices()
var tokenProvider = new RsaJwtTokenProvider("issuer", "audience", "mykeyname");
services.AddSingleton<ITokenProvider>(tokenProvider);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = tokenProvider.GetValidationParameters();
});
// This is for the [Authorize] attributes.
services.AddAuthorization(auth => {
auth.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});
Then Configure()
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
// Whatever else you're putting in here...
app.UseMvc();
}
That should be about all you need. Hopefully I haven't missed anything.
The happy result is...
[Authorize] // Yay!
[Route("api/values")]
public class ValuesController : Controller
{
// ...
}
Following on #Mitch answer: Auth stack changed quite a bit moving to .NET Core 2.0. Answer below is just using the new implementation.
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace JwtWithoutIdentity
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = "me",
ValidAudience = "you",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")) //Secret
};
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
}
}
Token Controller
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using JwtWithoutIdentity.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
namespace JwtWithoutIdentity.Controllers
{
public class TokenController : Controller
{
[AllowAnonymous]
[Route("api/token")]
[HttpPost]
public async Task<IActionResult> Token(LoginViewModel model)
{
if (!ModelState.IsValid) return BadRequest("Token failed to generate");
var user = (model.Password == "password" && model.Username == "username");
if (!user) return Unauthorized();
//Add Claims
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.UniqueName, "data"),
new Claim(JwtRegisteredClaimNames.Sub, "data"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")); //Secret
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken("me",
"you",
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new JsonWebToken()
{
access_token = new JwtSecurityTokenHandler().WriteToken(token),
expires_in = 600000,
token_type = "bearer"
});
}
}
}
Values Controller
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace JwtWithoutIdentity.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[Authorize]
[HttpGet]
public IEnumerable<string> Get()
{
var name = User.Identity.Name;
var claims = User.Claims;
return new string[] { "value1", "value2" };
}
}
}
Hope this helps!

MVC one-to-many database not updated

I have added an ApplicationCompany class to my MVC project like this:
public class ApplicationCompany
{
public Guid Id { get; set; }
public String Name { get; set; }
public virtual List<ApplicationUser> Users { get; set; }
public ApplicationCompany()
{
Id = Guid.NewGuid();
Users = new List<ApplicationUser>();
}
}
In the ApplicationUser class I have added:
public virtual ApplicationCompany Company { get; set; }
Now, when I debug an instance of ApplicationUser, I can see it has the Company property, and I can see the ApplicationCompany table in my database. When a user registers, I'd like to add a Company linked to them. I am doing the following but it is not saving the company:
using (var context = new ApplicationDbContext())
{
if (ModelState.IsValid)
{
var user = new ApplicationManager
{
UserName = model.Email,
Email = model.Email
};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var company = new ApplicationCompany();
user.Company = company;
context.SaveChanges();
...
}
}
}
Any ideas why?
The DbContext instance you use into the if (result.Succeeded) block doesn't know anything about the user instance you just set before calling context.SaveChanges(). Modify your code by attaching user instance into the context or just changing the state of the user to EntityState.Modified.
if (result.Succeeded)
{
var company = new ApplicationCompany();
user.Company = company;
context.Entry(user).State = EntityState.Modified;
context.Entry(company).State = EntityState.Added;
context.SaveChanges();
}
Edit 1:
Attaching an entity of type 'ApplicationUser' failed because another entity of the same type already has the same primary key value
Because another context own the user instance, then you must instantiate the DbContext into the if block (don't forget to suppress the root instance you create at the start of your method) like this:
if (result.Succeeded)
{
using (var context = new ApplicationDbContext())
{
var company = new ApplicationCompany();
user.Company = company;
context.Entry(user).State = EntityState.Modified;
context.Entry(company).State = EntityState.Added;
context.SaveChanges();
}
}
Edit 2: Overriding CreateAsync method in ApplicationUserManager class
The better approcach, IMHO, is to oveeride CreateAsync method in ApplicationUserManager class. by doing that you don't need to add the company updates into your controller's action.
public class ApplicationUserManager : UserManager<ApplicationUser>
{
// ...
public override async Task<IdentityResult> CreateAsync(ApplicationUser user, string password)
{
var company = new ApplicationCompany();
user.Company = company;
return await base.CreateAsync(user, password);
}
}
The problem is that, you are using a different DbContext object to insert the User than the one you are using to insert the company. You should be using the same DbContext object.
Change your UserManager.CreateAsync method to accept the DbContext object and use it there to add the new user
public Task<SomeDTO> CreateAsync(ApplicationDbContext db, ApplicationUser user,
string password)
{
//to do : Set the password
//Use db to add the new User
db.ApplicationUsers.Add(user);
await db.SaveChangesAsync();
// to do : return something
}
And when you call it from your action method, make sure to pass the DbContext.
var result = await UserManager.CreateAsync(context,user, model.Password);
That should fix your problem.

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.

Unit test controller - membership error

I want to create a Unit test for the following controller but it got fail in the Membership class:
public class AccountController:BaseController
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if(FormsService == null) { FormsService = new FormsAuthenticationService(); }
if(MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public ActionResult LogOn()
{
return View("LogOn");
}
[HttpPost]
public ActionResult LogOnFromUser(LappLogonModel model, string returnUrl)
{
if(ModelState.IsValid)
{
string UserName = Membership.GetUserNameByEmail(model.Email);
if(MembershipService.ValidateUser(model.Email, model.Password))
{
FormsService.SignIn(UserName, true);
var service = new AuthenticateServicePack();
service.Authenticate(model.Email, model.Password);
return RedirectToAction("Home");
}
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View("LogOn", model);
}
}
Unit test code:
[TestClass]
public class AccountControllerTest
{
[TestMethod]
public void LogOnPostTest()
{
var mockRequest = MockRepository.GenerateMock();
var target = new AccountController_Accessor();
target.Initialize(mockRequest);
var model = new LogonModel() { UserName = "test", Password = "1234" };
string returnUrl = string.Empty;
ActionResult expected = null;
ActionResult actual = target.LogOn(model, returnUrl);
if (actual == null)
Assert.Fail("should have redirected");
}
}
When I googled, I got the following code but I don't know how to pass the membership to the accountcontroller
var httpContext = MockRepository.GenerateMock();
var httpRequest = MockRepository.GenerateMock();
httpContext.Stub(x => x.Request).Return(httpRequest);
httpRequest.Stub(x => x.HttpMethod).Return("POST");
//create a mock MembershipProvider & set expectation
var membershipProvider = MockRepository.GenerateMock();
membershipProvider.Expect(x => x.ValidateUser(username, password)).Return(false);
//create a stub IFormsAuthentication
var formsAuth = MockRepository.GenerateStub();
/*But what to do here???{...............
........................................
........................................}*/
controller.LogOnFromUser(model, returnUrl);
Please help me to get this code working.
It appears as though you are using concrete instances of the IMembershipServive and IFormsAuthenticationService because you are using the Accessor to initialize them. When you use concrete classes you are not really testing this class in isolation, which explains the problems you are seeing.
What you really want to do is test the logic of the controller, not the functionalities of the other services.
Fortunately, it's an easy fix because the MembershipService and FormsService are public members of the controller and can be replaced with mock implementations.
// moq syntax:
var membershipMock = new Mock<IMembershipService>();
var formsMock = new Mock<IFormsAuthenticationService>();
target.FormsService = formsMock.Object;
target.MembershipService = membershipService.Object;
Now you can test several scenarios for your controller:
What happens when the MembershipService doesn't find the user?
The password is invalid?
The user and password is is valid?
Note that your AuthenticationServicePack is also going to cause problems if it has additional services or dependencies. You might want to consider moving that to a property of the controller or if it needs to be a single instance per authentication, consider using a factory or other service to encapsuate this logic.

Resources