I write a new ASP.NET MVC 5 application and I have some problems with the authentication. I want to have two registration and login forms (for users and for companies). I use the basic table ApplicationUser for Users and make my own table CompaniesAccountModel for companies. But the problem comes when I use the UserManager and SignInManager. I can't modify them to work with CompaniesAccountModel. Here you are some code.
[AllowAnonymous]
public ActionResult CompanyRegister()
{
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult CompanyRegister([Bind(Include = "CompanyName, Password, Email, ConfirmPassword")] CompanyAccountModel model)
{
if (ModelState.IsValid)
{
db.CompanyAccountModels.Add(model);
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
// If we got this far, something failed, redisplay form
return View(model);
}
and
[AllowAnonymous]
public ActionResult CompanyLogin(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> CompanyLogin(CompanyLoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.CompanyName, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
I want to use the UserManager and SignInManager for the companies registration and login. If someone have an idea how to do this, it would be well.
You could easily customize authentication process for your company users. And use it side by side with existing method for ordinary users. Consider this example as a clue:
public ActionResoult CompanyLogin(CompanyLoginViewModel model, string returnUrl)
{
// imaging you have own company manager, completely independent from identity
// you could check validity of company by own preferred logic
if(_companyManager.IsValid(model))
{
// company is valid, going to authenticate
var ident = new ClaimsIdentity(
new[]
{
// adding following 2 claim just for supporting default antiforgery provider
new Claim(ClaimTypes.NameIdentifier, model.CompanyName),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
// an optional claim you could omit this
new Claim(ClaimTypes.Name, model.CompanyName),
// add this role to differentiate from ordinary users
new Claim(ClaimTypes.Role, "Company"),
// you could even add some role
new Claim(ClaimTypes.Role, "AnotherRole"),
// and so on
},
DefaultAuthenticationTypes.ApplicationCookie);
// Identity is sign in user based on claim don't matter
// how you generated it Identity
HttpContext.GetOwinContext().Authentication.SignIn(
new AuthenticationProperties { IsPersistent = false }, ident);
// auth is succeed,
return RedirectToAction("MyAction");
}
ModelState.AddModelError("", "We could not authorize you :(");
return View();
}
Since we injected our logic to Identity, we don't need to do extra thing at all.
[Authorize]
public ActionResult MySecretAction()
{
// all authorized users could use this method don't matter how has been authenticated
// we have access current user principal by calling also
// HttpContext.User
}
[Authorize(Roles="Company")]
public ActionResult MySecretAction()
{
// just companies have accesses to this method
}
Also if both ApplicationUser and Company classes share lots in common you could just extend Company from ApplicationUser. By doing so you don't need to write extra login method. Same login works for both. But if for any reason you don't want inherit Company from ApplicationUser my above solution more desirable.
Related
I'm working on a system that uses ASP.NET MVC 5 and Identity 2 with Entity Framework 6.
When a user logs in, I add some claims to that login session. I don’t want to use the claims table.
For one of my claims, I did like this:
public class User : IdentityUser<int, UserLogin, UserRole, UserClaim>
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User, int> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
//We add the display name so that the _LoginPartial can pick it up;
userIdentity.AddClaim(new Claim("DisplayName", FirstName + " " + LastName));
// Add custom user claims here
return userIdentity;
}
public virtual ICollection<UserInsurance> UserInsurances { get; set; }
public User()
{
UserInsurances = new List<UserInsurance>();
}
}
And for accessing the claim:
var claimsIdentity = User.Identity as System.Security.Claims.ClaimsIdentity;
var displayNameClaim = claimsIdentity != null
? claimsIdentity.Claims.SingleOrDefault(x => x.Type == "DisplayName")
: null;
var nameToDisplay = displayNameClaim == null ? User.Identity.Name : displayNameClaim.Value;
This works well. But the problem is when I need a field that is not in the User table. In fact, it is one record in the user’s navigation property (UserInsurances) and I need a linq query for access that.
var lastUserInsurance = UserInsurances.OrderByDescending(x => x.CompanyInsuranceId).First();
userIdentity.AddClaim(new Claim("CompanyInsuranceId", lastUserInsurance.CompanyInsuranceId.ToString()));
If I put this code in GenerateUserIdentityAsync method like “DisplayName”, UserInsurances is null. So I should add this code to login action and after the user logs in successfully. But I tried that and it doesn’t work. I don’t know why but when I want to access that claim, it does not exist.
public virtual async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
var user = _user.Include(x => x.UserInsurances).FirstOrDefault(x => x.NationalCode == model.UserName);
var identity = await SignInManager.CreateUserIdentityAsync(user);
var lastUserInsurance = user.UserInsurances.OrderByDescending(x => x.CompanyInsuranceId).FirstOrDefault();
identity.AddClaim(new Claim("CompanyInsuranceId", lastUserInsurance.CompanyInsuranceId.ToString()));
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
return View(model);
}
}
Can anyone tell me why I can’t access this claim and it does not exist? I don’t know how I implement this scenario and access “CompanyInsuranceId” claim in all parts of my application.
You must add your claims before sign the user in. So if for any reason you can not fill your claims in GenerateUserIdentityAsync method. Simply generate Identity object in the log in action method then sign in it. Consider this example:
public async Task<ActionResult> Login(LoginViewModel model,string returnUrl)
{
var user = UserManager.Find(model.Email, model.Password);
// now you have the user object do what you to gather claims
if(user!=null)
{
var ident = UserManager.CreateIdentity(user,
DefaultAuthenticationTypes.ApplicationCookie);
ident.AddClaims(new[] {
new Claim("MyClaimName","MyClaimValue"),
new Claim("YetAnotherClaim","YetAnotherValue"),
});
AuthenticationManager.SignIn(
new AuthenticationProperties() { IsPersistent = true },
ident);
return RedirectToLocal(returnUrl);
}
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
As you can see you could do anything you want to gather claims and fill the identity then sign in the user.
But if you want use SignInManager.PasswordSignInAsync() method simply override SignInManager.CreateUserIdentityAsync() method in a way so you could generate desired claims. For example if you need DbContext to fetch extra information for feeding your claims simply you could inject DbContext in SignInManager and use it in CreateUserIdentityAsync() method like this:
public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
private readonly ApplicationDbContext _context;
public ApplicationSignInManager(
ApplicationUserManager userManager,
IAuthenticationManager authenticationManager,
ApplicationDbContext context)
: base(userManager, authenticationManager)
{
_context=context;
}
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
var companyInsuranceId=_context.Users
.Where(u=>u.NationalCode == user.UserName)
.Select(u=>u.UserInsurances
.OrderByDescending(x => x.CompanyInsuranceId)
.Select(x=>x.CompanyInsuranceId)
.FirstOrDefault())
.FirstOrDefault();
var ident=user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
ident.AddClaim(new Claim("CompanyInsuranceId",
companyInsuranceId.ToString()));
return ident;
}
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
{
return new ApplicationSignInManager(
context.GetUserManager<ApplicationUserManager>(),
context.Authentication,
context.Get<ApplicationDbContext>());
}
}
Now simply just by writing
var result = await SignInManager.PasswordSignInAsync(
model.UserName, model.Password,
model.RememberMe, shouldLockout: false);
you could sign the user in and inject additional claims.
For MVC5, additional claims can be added easily via the ApplicationUser class.
eg.
public ClaimsIdentity GenerateUserIdentity(ApplicationUserManager manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = manager.CreateIdentity(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
This also resolve
Identity cookie loses custom claim information after a period of time
Using MVC 5.1.1/VS 2013 Professional/.Net 4.5
I keep getting error once in a while (from localhost and from production IIS 7):
System.Web.Mvc.HttpAntiForgeryException: The anti-forgery cookie token and form field token do not match.
The issue seems to be when i logout a user, sometimes when i go to authenticate again thats when i get the error.
My authentication code looks like something like this:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
var user = _uow.UserRepository.FindLogin(model.Email, model.Password);
if (user != null)
{
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Email, user.Email));
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.UserID.ToString()));
claims.Add(new Claim(ClaimTypes.Role, user.UserLevel.ToString()));
var id = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true }, id);
}
else
{
ModelState.AddModelError("", "Invalid Email Address or Password.");
}
}
return View(model);
}
Update with LogOut Method:
[HttpGet]
[AllowAnonymous]
public ActionResult LogOut(LoginViewModel model)
{
Session.Abandon();
return SignOffUser();
}
private ActionResult SignOffUser()
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return RedirectToAction("Index", "Home");
}
LogOut Form View
#if (Request.IsAuthenticated)
{
using (Html.BeginForm(null, null, FormMethod.Post, new {id = "logoutForm", #class = "navbar-right"}))
{
#Html.AntiForgeryToken()
....
}
}
Another thing you may want to look at is that on your logout page, you don't necessary validate the forgery token.
Try changing this:
[HttpGet]
[AllowAnonymous]
public ActionResult LogOut(LoginViewModel model)
{
To this
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult LogOut(LoginViewModel model)
{
My bet is that the token is changing on the page, and since it's not validated, ASP.NET doesn't know if it is truly correct or not.
E: just noticed, it should actually be an httppost on logout
Show your logout form(view).
This happens if you are calling the logout method in your controller from your view but don't have antiforgerytoken generated inside the form.
Your form should look like
#using(Html.BeginForm("Action","Controller"))
{
#Html.antiforgerytoken()
....
}
Then the action you call via your view should look like
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MyAction()
{
return content("hello user! Antiforgerytoken has been validated1");
}
PS: No matter what action method your form calls, if you want to have antiforgery token that needs to be validated, then the form and action method should look like what I've mentioned above.
Try setting explicitly machine key in web.config (under system.web):
<machineKey validationKey="971E32D270A381E2B5954ECB4762CE401D0DF1608CAC303D527FA3DB5D70FA77667B8CF3153CE1F17C3FAF7839733A77E44000B3D8229E6E58D0C954AC2E796B" decryptionKey="1D5375942DA2B2C949798F272D3026421DDBD231757CA12C794E68E9F8CECA71" validation="SHA1" decryption="AES" />
Here's a site that generate unique Machnie Keys http://www.developerfusion.com/tools/generatemachinekey/
link: The anti-forgery cookie token and form field token do not match in MVC 4
I don't want to repeat myself. That is, I don't want the same code in two different controllers.
I always start from a default mvc5 web app project. That project has a Register ActionMethod in an AccountController:
//
// GET: /Account/Register
[AllowAnonymous]
public ActionResult Register()
{
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Say I have a CampaignController and I want to register a user when he/she is on that page, fills out his/her username and pass and clicks the send form/submit button. What is the best thing to do in the ActionMethod of that form/controller?
Yes, I want to have the registerform in two or more places.
What is the best way to accomplish this in mvc 5?
Create a logic layer
put the register logic in that logic layer
add the logic layer as reference in the web layer
hit the function in the logic layer from both actionresults :)
In your example at least... I would call the actions of your AccountController from your Campaign views.
For me it working like a charm after setting the current ControllerContext to AccountControllerContext
//This is employee controller class
public ActionResult Create([Bind(Include = "EmployeeId,FirstName,LastName,DOJ,DOB,Address,City,State,Mobile,Landline,ReportsTo,Salary")] Employee employee)
{
if (ModelState.IsValid)
{
AccountController accountController = new AccountController();
accountController.ControllerContext = this.ControllerContext;
//accountController.UserManager;
var userId = accountController.RegisterAccount(new RegisterViewModel { Email = "temp#temp.com", Password = "Pravallika!23" });
if (!string.IsNullOrEmpty(userId))
{
employee.UserId = userId;
employee.CreatedBy = User.Identity.GetUserId();
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
}
//customized method in AccountController
public string RegisterAccount(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = UserManager.Create(user, model.Password);
//to add roles
//UserManager.AddToRole(user.Id, "Admin");
if (result.Succeeded)
{
return user.Id;
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed
return null;
}
In VisualStudio 2013 RC, using MVC5, in AccountController.cs, I had to modify the Login method to call WebSecurity.Login as I'm still using WebSecurity and Webmatrix to add users and user roles. I populate users and roles with WebSecurity and Webmatrix API using an Entity Framework 6 seed method. In my code examples WebSecurity.Login will handle a local user account, and CheckPasswordAndSignInAsync will handle a Google account login.
Should OWIN be a replacement for Webmatrix and the Websecurity API, if so what is Microsoft's OWIN API to create roles and users? Can't seem to find much documentation on OWIN in MSDN. Has anyone created any sample code or aware of any docs that explain
how to use Microsofts' flavor of OWIN? Do you have to write your own code to populate
roles and users to the OWIN generated tables such as AspNetUsers and AspNetUserRoles?
This is login method:
public async Task Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
// Validate the password
IdentityResult result = await IdentityManager.Authentication.CheckPasswordAndSignInAsync(AuthenticationManager, model.UserName, model.Password, model.RememberMe);
if (result.Success)
{
return RedirectToLocal(returnUrl);
}
//added this for roles to work
else if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
return RedirectToLocal(returnUrl);
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
This is signout method:
// POST: /Account/LogOff
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
WebSecurity.Logout();
return RedirectToAction("Index", "Home");
}
In order to create a user, you need to write code like this:
UserManager<ApplicationUser> userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
var user = new ApplicationUser() { UserName = model.UserName };
// Add the Admin role to the user.
user.Roles.Add(new IdentityUserRole() { Role = new IdentityRole("Admin") });
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// Code after successfully creating the user.
}
So I am currently using a ViewBag set in the login to determine if they can see admin only stuff. This is done this way because Roles.CreateRole, Membership.CreateUser and Roles.AddUserToRole is disabled because we use ModelFirst ASP.net.
public ActionResult Login(LoginModel model, string returnUrl)
{
ViewBag.Admin = false;
if (model.IsValid(model.UserName, model.Password))
{
ViewBag.Admin = (bool)model.currentLoggedInEmployee.IsAdmin;
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Login data is incorrect!");
return View(model);
}
}
Then we simply use:
#if (ViewBag.Admin == true) {
<li>#Html.ActionLink("Administration", "Index", "Administration")</li>
}
to only show these buttons to admins. This works.
Now what we want, is to make sure only administrators can run some functions, by doing something similar to the normal
[Authenticate(Roles="Admin")]
[HttpPost]
public ActionResult Create(FormCollection collection)
{
// TODO: Add insert logic here
}
But because we don't have any "Roles" we can not do it like this. We need to use the ViewBag.Admin value to authorize people to use these functions. Question is, how can this be done?
I would recommend rolling your own AuthorizeAttribute and from there you can determine whether or not the current logged in user is an admin or not.
When you create your authentication cookie add some additional information (i.e. the admin flag) e.g.
public ActionResult Login(LoginModel model, string returnUrl)
{
if (model.IsValid(model.UserName, model.Password))
{
var ticket = new FormsAuthenticationTicket(1,
model.UserName,
DateTime.Now,
DateTime.Now.AddMinutes(30),
model.RememberMe,
model.currentLoggedInEmployee.IsAdmin, // user data
FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
// Redirect back to original URL.
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Login data is incorrect!");
return View(model);
}
}
Create a custom authorize attribute to authenticate the logged in user against the role e.g.
public class AdminOnlyAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Current.User.Identity.IsAuthenticated)
{
var ticket = ((FormsIdentity)User.Identity).Ticket;
return (bool)ticket.UserData;
}
else
{
return false;
}
}
}
Then decorate your action as:
[AdminOnly]
[HttpPost]
public ActionResult Create(FormCollection collection)
{
// TODO: add insert logic here
}