Register with unique email OR unique username in ASP.NET Core MVC - asp.net

I am working on a system that should allow users to register by a unique username, or unique email. Register by both is possible as well.
I am using the default identity pages with some modifications, here is a sample of the register code:
public class InputModel
{
//[Required] //Validated in server side based on role
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
//[Required] //Validated in server side based on role
[Display(Name = "Username")]
public string UserName { get; set; }
[Required]
[Display(Name = "Name")]
public string Name { get; set; }
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
string username = Input.UserName ?? Input.Email;
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = username,
Email = Input.Email,
Name = Input.Name,
};
}
}
Basically, if the user entered a username, Username column will be Input.UserName.
if no username (only email), the Username = Input.Email, because obviously it cannot be empty. Now both username and email are equal as the default.
examples:
Username: a#a , Email: a#a , >> no username
Username: xyz , Email: null , >> no email
Username: abc , Email: a#b , >> user entered both username and email
For now, username is always unique and always required (required by identity not the user), but not the case for the email, it can be null as expected but its not unique, I added this line in the startup.cs for the uniqueness:
services.AddIdentity<IdentityUser, IdentityRole>(options => {
options.User.RequireUniqueEmail = true;
})
but now it cannot be null, it give this validation error:
Email '' is invalid.

Please try to implement a setter for Email that converts empty string to null

I added this line in the startup.cs for the uniqueness: options.User.RequireUniqueEmail = true;
but now it cannot be null, it give this validation error: Email '' is invalid.
In source code of UserManager<TUser>.CreateAsync method, we can find that it will call ValidateUserAsync method to valid user before saving the user.
var result = await ValidateUserAsync(user);
in code of ValidateUserAsync method, we can find it call another method ValidateAsync, like below.
var result = await v.ValidateAsync(this, user);
and in ValidateAsync method, if we configured RequireUniqueEmail to true, it will valid Email of current user by calling ValidateEmail method.
if (manager.Options.User.RequireUniqueEmail)
{
await ValidateEmail(manager, user, errors);
}
Go to definition of ValidateEmail method, we can easily find it will check if the email parameter is null or System.String.Empty, or if value consists exclusively of white-space characters.
// make sure email is not empty, valid, and unique
private async Task ValidateEmail(UserManager<TUser> manager, TUser user, List<IdentityError> errors)
{
var email = await manager.GetEmailAsync(user);
if (string.IsNullOrWhiteSpace(email))
{
errors.Add(Describer.InvalidEmail(email));
return;
}
if (!new EmailAddressAttribute().IsValid(email))
{
errors.Add(Describer.InvalidEmail(email));
return;
}
var owner = await manager.FindByEmailAsync(email);
if (owner != null &&
!string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user)))
{
errors.Add(Describer.DuplicateEmail(email));
}
}

you can try to validate if the email or username already exist by using FindByNameAsync or FindByEmailAsync by using the UserManager. If the return is null then it means that the username/email doesn't exist yet.
Here is the sample approach for this:
//private readonly UserManager<IdentityUser> _userManager ;
var isUniqueUserName = await _userManager.FindByNameAsync(UserNameFromInput)==null;
var isUniqueEmail = await _userManager.FindByEmailAsync(EmailFromInput) == null;
if (!isUniqueUserName || !isUniqueEmail)
{
if(!isUniqueUserName)
ModelState.AddModelError("UserName", "UserName already exist.");
if(!isUniqueEmail)
ModelState.AddModelError("Email", "Email already exist.");
return View(registerViewModel);
}

Related

asp.net mvc multitenant database per tenant

I'm building an app for our company which needs to have separate database per client. App is for the usage of other multiple companies, so the app needs to identify the company name when the user logs in and make the users operate only within their company db.
I have it all set, but the problem is that the app is not able to handle 2 different databases simultaneously. When users from two different companies log in, the first users db gets changed to the db of the second user who is logged in! This is of course unacceptable. How can I make the app to use 2 dbs simultaneously?
I have one database which collects all app users and their company names and separate databases for each company. I also have a standard asp
below are my codes:
Login class and database initializer in account controller
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
bool ifDemo = false;
string demoPrefix = "demo.";
if (model.Email.StartsWith(demoPrefix))
{
ifDemo = true;
model.Email = model.Email.Substring(demoPrefix.Length);
}
SetDatabaseInitializerAndInitializeIt(ifDemo, model.Email);
// 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.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
returnUrl = CheckFirstLogin(model.Email, returnUrl);
await OnSignInSuccess(model);
//FormsAuthentication.SetAuthCookie(model.Email, false);
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);
}
}
public static void SetDatabaseInitializerAndInitializeIt(bool demoDB, string login)
{
Database.SetInitializer(new ApplicationUsersSeedData());
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Facility.Data.FacilityEntities,
Facility.Migrations.Configuration>());
// Check current users company name and set the right database.
// To use demo version of database add "demo." prefix to the login email e.g: demo.testspamu79#gmail.com
using (var domain = new Facility.Models.UserCommonDBContext())
{
domain.Database.Initialize(true);
}
UserCommonDBContext context = new Facility.Models.UserCommonDBContext();
var companyName = context.CommonUser.Where(x => x.CommonUserEmail == login).FirstOrDefault().CommonUserCompanyName;
if (demoDB)
companyName = companyName + "Demo";
using (var domain = new Facility.Data.FacilityEntities(companyName))
{
domain.Database.Initialize(true);
}
}
Dbcontext:
public partial class FacilityEntities : DbContext
{
public static string DbName;
public FacilityEntities() : base(string.IsNullOrWhiteSpace(DbName) ? "Demo" : DbName)
{
}
public FacilityEntities(string dbName) : base(dbName)
{
DbName = dbName;
}
as Tielson T. said in the comments, I got rid of static and stored db name in session, and now it works!

How to get role name for user in Asp.Net Identity

I am trying to figure out how to find user role name in identity framework.
I have such configuration that there will be only one role assigned to a user.
So, I tried using
public string GetUserRole(string EmailID, string Password)
{
var user = await _userManager.FindAsync(EmailID, Password);
var roleid= user.Roles.FirstOrDefault().RoleId;
}
But what I get is just RoleId and not RoleName.
Can anyone help me to find the RoleName for the user?
In your code, user object represents the AspNetUsers table which has a navigation property Roles which represents the AspNetUserInRoles table and not the AspNetRoles table.
So when you try to navigate through
user.Roles.FirstOrDefault()
it gives you the AspNetUserInRoles which stores UserId and RoleId.
Instead you could try UserManger's GetRoles method which will return you List<string> of roles user is assigned. But as you mentioned it will be only one role hence you can take first value from the result of GetRoles method.
Your function should be similar to one given below:
public async string GetUserRole(string EmailID, string Password)
{
var user = await _userManager.FindAsync(EmailID, Password);
string rolename = await _userManager.GetRoles(user.Id).FirstOrDefault();
return rolename;
}
If you has a list of users you has to do this:
var usuarioManager = Request.GetOwinContext().GetUserManager<UserManager<Usuario>>();
var roleManager = Request.GetOwinContext().Get<RoleManager<IdentityRole>>();
var roles = roleManager.Roles.ToList();
var usuarios = usuarioManager.Users.Include(x => x.Roles).ToList();
usuarios.ForEach((x) =>
{
if (x.Roles.Any())
{
var roleDb = roles.FirstOrDefault(r => r.Id == x.Roles.FirstOrDefault().RoleId);
if (roleDb != null)
x.RoleName = roleDb.Name;
}
});

Use Username instead of Email for identity in Asp.net mvc5

Whenever I create a new application with visual studio 2013 express for web and using the individual accounts authentication and i hit the register button I notice that it implements 'Email' instead of 'Username' and the same is in the LoginViewModel as it uses Email to Sign in instead of Username. How can i change this to use Username instead of the default Email without trouble? Also i would like to know how to convert the default 'guid' that is a string type to 'id' (integer type).
The linked question in the accepted answer descibes how to use Email instead of UserName, OP wanted the UserName instead of email which is what I was looking for in Identity 2.0 in an MVC project.
In case anyone else gets here from a google search it is actually very easy to do this. If you look at the register post action it is simply setting the UserName to the Email address. So.........
Add UserName to the RegisterViewModel and add it to the register view.
<div class="form-group">
#Html.LabelFor(m => m.UserName, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.UserName, new { #class = "form-control", #placeholder = "Username or email" })
</div>
</div>
In the Register Post Action on the AccountController set the UserName to the ViewModel's UserName and the Email to the ViewModel's Email.
var user = new ApplicationUser { UserName = model.UserName, Email = model.Email };
In order to make email as non unique:
Configure below in IdentityConfig
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = false
};
Please look into this thread
Thread Details :
Assumptions:
Username is unique for each user. It is either input by user or generated by application on registration.
No # symbol allowed in Username.
Remove EmailAddress annotation and define Display text in the default LoginViewModel:
public class LoginViewModel
{
[Required]
[Display(Name = "Username/Email")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
As user can enter either Username or Email, so we will make # character for validation criteria. Here is the flow to be implemented:
If in the string # is present, apply Email validation else apply Username format validation.
In case of valid Email, first we need to get Username. As it is considered that Username is unique so we can get it with userManager.FindByEmailAsync method.
Use Username for SignIn verification.
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (model.Email.IndexOf('#') > -1)
{
//Validate email format
string emailRegex = #"^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}" +
#"\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\" +
#".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$";
Regex re = new Regex(emailRegex);
if (!re.IsMatch(model.Email))
{
ModelState.AddModelError("Email", "Email is not valid");
}
}
else
{
//validate Username format
string emailRegex = #"^[a-zA-Z0-9]*$";
Regex re = new Regex(emailRegex);
if (!re.IsMatch(model.Email))
{
ModelState.AddModelError("Email", "Username is not valid");
}
}
if (ModelState.IsValid)
{
var userName = model.Email;
if (userName.IndexOf('#') > -1)
{
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
else
{
userName = user.UserName;
}
}
var result = await _signInManager.PasswordSignInAsync(userName, model.Password, model.RememberMe, lockoutOnFailure: false);
No special need to change in View. Run the application and test login with Email or Username.
Note: Keep in mind this tutorial follows MVC .Net Identity default structure.
You can also use username and/or password like this
var user = await _userManager.Users
.FirstOrDefaultAsync(u => u.UserName == username || u.Email == username);
if (user != null){
var result = await _signInManager
.PasswordSignInAsync(user.Email, password, false, false);
}

Adding user data to manage page in MVC5

I've been following the Add Profile Data to User Class part of this tutorial to add more fields to my registration page in MVC 5. So far it's working fine and I have no issues. The problem is now I'm not sure on how that gets displayed on the Manage page where the user can see his profile info like changing his password. For example, I want to add on a first and last name on that page.
http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on#ap
Here is a screenshot of the page I'm talking about:
http://puu.sh/dcMmj/82ddd8fc97.PNG
My project is what Visual Studio creates for you with the added first and last name in the registration page. Added this in the following Identity Model like in the tutorial
public class ApplicationUser : IdentityUser
{
public string FirstName{ get; set; }
public string LastName { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
I'm thinking I need to add something here in the ManageController.cs but I'm not sure.
// GET: /Manage/Index
public async Task<ActionResult> Index(ManageMessageId? message)
{
ViewBag.StatusMessage =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: message == ManageMessageId.SetTwoFactorSuccess ? "Your two-factor authentication provider has been set."
: message == ManageMessageId.Error ? "An error has occurred."
: message == ManageMessageId.AddPhoneSuccess ? "Your phone number was added."
: message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed."
: "";
var model = new IndexViewModel
{
HasPassword = HasPassword(),
PhoneNumber = await UserManager.GetPhoneNumberAsync(User.Identity.GetUserId()),
TwoFactor = await UserManager.GetTwoFactorEnabledAsync(User.Identity.GetUserId()),
Logins = await UserManager.GetLoginsAsync(User.Identity.GetUserId()),
BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(User.Identity.GetUserId()),
// I think it goes in here somewhere
};
return View(model);
}
You can replace your var model with this code:
ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
var model = new IndexViewModel {
HasPassword = HasPassword(),
PhoneNumber = user.PhoneNumber,
TwoFactor = user.TwoFactorEnabled,
Logins = await UserManager.GetLoginsAsync(user),
BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(user),
FirstName = user.FirstName
}
Now you can use model.FirstName in your View.

MVC5 ApplicationUser custom properties

I am trying to get to grips with the new Membership system introduced in ASP.NET MVC 5 and I've come across a small issue which I am pretty sure you will be able to help me with.
I am going based off this tutorial and have introduced custom properties to ApplicationUser such as Name, Surname, DOB, etc.
However, instead of creating the user, I am trying to update the currently logged in one. I am looking at the controller method which is currently used to change password.
public async Task<ActionResult> Manage(ManageUserViewModel model)
{
string userId = User.Identity.GetUserId();
bool hasLocalLogin = await IdentityManager.Logins.HasLocalLoginAsync(userId);
ViewBag.HasLocalPassword = hasLocalLogin;
ViewBag.ReturnUrl = Url.Action("Manage");
if (hasLocalLogin)
{
if (ModelState.IsValid)
{
IdentityResult result = await IdentityManager.Passwords.ChangePasswordAsync(User.Identity.GetUserName(), model.OldPassword, model.NewPassword);
if (result.Success)
{
return RedirectToAction("Manage", new { Message = "Your password has been changed." });
}
else
{
AddErrors(result);
}
}
}
else
{
// User does not have a local password so remove any validation errors caused by a missing OldPassword field
ModelState state = ModelState["OldPassword"];
if (state != null)
{
state.Errors.Clear();
}
if (ModelState.IsValid)
{
// Create the local login info and link it to the user
IdentityResult result = await IdentityManager.Logins.AddLocalLoginAsync(userId, User.Identity.GetUserName(), model.NewPassword);
if (result.Success)
{
return RedirectToAction("Manage", new { Message = "Your password has been set." });
}
else
{
AddErrors(result);
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
How exactly would I go on about updating an ApplicationUser's Surname for example? Do I need to call the DbContext or?
I hope my question is clear.
Explore IdentityManager.Store.UserManagement and IdentityManager.Store.Users.
ApplicationUser cUser = (ApplicationUser) await IdentityManager.Store.Users.FindByNameAsync(HttpContext.User.Identity.Name, new System.Threading.CancellationToken());
cUser.Surname = "New Something";
IdentityResult result1 = await IdentityManager.Store.SaveChangesAsync();
Above code is an example only. Basically you need to explore the Store property of IdentityManage.
When we used the Users object of our database context we ran into other tracking errors. In our application, we would retrieve users as such
var user = UserManager.FindById(userId);
Edit the properties:
user.StorageName = "gooblygook";
//whatever other properties you would like to use
And then we would save it with the UserManager in the controller:
UserManager.Update(user);
This is currently a working solution for us.
Mark the Person object as virtual in your ApplicationUser definition. That worked for me.
public class ApplicationUser : IdentityUser
{
public virtual Person Person { get; set; }

Resources