.Net Core Identity Add Company Name As Part Of User Login Process In .Net Core 5.0 - authentication

Is it possible to use additional user fields for user login in .Net Core Identity? I want to allow logging in users using email, password, and a third field i.e Company Name which is a custom field I defined in ApplicationUser class inherited from IdentityUser class.
My ApplictionUser class:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string CompanyName { get; set; }
}
It is working fine. I can add the company name for the user during registration.
Here is the identity InputModel code which is created by identity scaffolding:
public class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Required]
[Display(Name = "Company Name")]
public string CompanyName { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
I added the CompanyName extra field here. Now I want to log in the user based on these fields. If the company name provided by the user on the login page is correct, allow the user to login otherwise not.
I am doing the following custom code inside OnPostAsync method of Login.cshtml.cs file:
var user = await _userManager.FindByEmailAsync(Input.Email);
ApplicationUser user = new ApplicationUser
{
Email = Input.Email,
UserName = Input.Email,
CompanyName = Input.CompanyName,
};
var result = await _signInManager.PasswordSignInAsync(user, Input.Password, Input.RememberMe, lockoutOnFailure: false);
But the result is always getting failed for every attempt. Am doing anything wrong here?

You appear to be overwriting the user object with your new implementation, which wouldn't have the password hash to check against (amongst other things).
It may be simpler to check the user for the correct company name directly and throw the error if it doesn't match, as follows:
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null || user.CompanyName != Input.CompanyName) {
ModelState.AddModelError("", "Invalid login attempt");
return View(model);
}
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);

Related

Invalid object name 'RefreshToken

i am trying to make a login system with jwt and refresh token using sql as db and asp.net core as frontend.
and i get below error. in data base i have made user table but not RefreshToken table as RefreshToken is going to generated and saved on cookie of the browser.
Invalid object name 'RefreshToken
here is my Model User
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
[JsonIgnore]
public string Password { get; set; }
[JsonIgnore]
public List<RefreshToken> RefreshTokens { get; set; }
}
here is another model RefreshToken
using Microsoft.EntityFrameworkCore;
using System;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace Api_R.Entities
{
[Owned]
public class RefreshToken
{
[Key]
[JsonIgnore]
public int Id { get; set; }
public string Token { get; set; }
public DateTime Expires { get; set; }
/////////
}
}
and here i am using my controller
public AuthenticateResponse Authenticate(AuthenticateRequest model, string ipAddress)
{
var user = _apiDbContext.User.SingleOrDefault(x => x.Username == model.Username && x.Password==model.Password);
// validate
if (user == null)//|| !BCryptNet.Verify(model.Password, user.PasswordHash))
throw new AppException("Username or password is incorrect");
// authentication successful so generate jwt and refresh tokens
var jwtToken = _jwtUtils.GenerateJwtToken(user);
var refreshToken = _jwtUtils.GenerateRefreshToken(ipAddress);
user.RefreshTokens.Add(refreshToken);
// remove old refresh tokens from user
removeOldRefreshTokens(user);
return new AuthenticateResponse(user, jwtToken, refreshToken.Token);
}
and now i am getting error invalid object name refresh token

"DbUpdateException: An error occurred while updating the entries" Error after implementing custom identity class

I am trying to create user registration form on .net core using DbFirst approach.
I created a table in database. Created .net core project using User Authentication(built in identity). Then I did Scaffolding. After Filling fields with proper values and posting the request, I get this error:
An unhandled exception occurred while processing the request.
SqlException: Invalid column name 'AccessFailedCount'. Invalid column
name 'ConcurrencyStamp'. Invalid column name 'LockoutEnabled'. Invalid
column name 'LockoutEnd'. Invalid column name 'NormalizedEmail'.
Invalid column name 'NormalizedUserName'. Invalid column name
'PasswordHash'. Invalid column name 'PhoneNumber'. Invalid column name
'PhoneNumberConfirmed'. Invalid column name 'SecurityStamp'. Invalid
column name 'TwoFactorEnabled'. Invalid column name 'UserName'.
DbUpdateException: An error occurred while updating the entries. See
the inner exception for details.
The fields mentioned above are in read-only file called IdentityUser(the one I am inheriting from). It's read-only so I can not delete any fields from it.
This is my Register IActionResult:
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
var errors = ModelState.Where(c => c.Value.Errors.Count > 0).Select(c => c.Value).ToList();
if (ModelState.IsValid)
{
var user = new User
{
Name = model.Name,
Email = model.Email,
Address = model.Address,
PersonalId = model.PersonalId,
Country = model.Country,
MobilePhone = model.MobilePhone,
BirthDate = model.BirthDate,
};
_context.User.Add(user);
_context.SaveChanges();
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action("ConfirmEmail", "Home", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
return RedirectToAction("RegisterConfirm");
}
}
return View(model);
}
I am guessing, that problem is that the fields mentioned in IdentityUser are empty and that is why I am getting the error but maybe I am wrong.
User.cs
public partial class User :IdentityUser<int>
{
public int Id { get; set; }
public string Country { get; set; }
public string PersonalId { get; set; }
public string MobilePhone { get; set; }
public string Email { get; set; }
public DateTime BirthDate { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public byte[] Idimage { get; set; }
public bool? EmailConfirmed { get; set; }
public bool? Smsconfirmed { get; set; }
}
IdentityUser
namespace Microsoft.AspNetCore.Identity
{
//
// Summary:
// Represents a user in the identity system
//
// Type parameters:
// TKey:
// The type used for the primary key for the user.
public class IdentityUser<TKey> where TKey : IEquatable<TKey>
{
//
// Summary:
// Initializes a new instance of Microsoft.AspNetCore.Identity.IdentityUser`1.
public IdentityUser();
//
// Summary:
// Initializes a new instance of Microsoft.AspNetCore.Identity.IdentityUser`1.
//
// Parameters:
// userName:
// The user name.
public IdentityUser(string userName);
//
// Summary:
// Gets or sets the date and time, in UTC, when any user lockout ends.
//
// Remarks:
// A value in the past means the user is not locked out.
public virtual DateTimeOffset? LockoutEnd { get; set; }
//
// Summary:
// Gets or sets a flag indicating if two factor authentication is enabled for this
// user.
[PersonalData]
public virtual bool TwoFactorEnabled { get; set; }
//
// Summary:
// Gets or sets a flag indicating if a user has confirmed their telephone address.
[PersonalData]
public virtual bool PhoneNumberConfirmed { get; set; }
//
// Summary:
// Gets or sets a telephone number for the user.
[ProtectedPersonalData]
public virtual string PhoneNumber { get; set; }
//
// Summary:
// A random value that must change whenever a user is persisted to the store
public virtual string ConcurrencyStamp { get; set; }
//
// Summary:
// A random value that must change whenever a users credentials change (password
// changed, login removed)
public virtual string SecurityStamp { get; set; }
//
// Summary:
// Gets or sets a salted and hashed representation of the password for this user.
public virtual string PasswordHash { get; set; }
//
// Summary:
// Gets or sets a flag indicating if a user has confirmed their email address.
[PersonalData]
public virtual bool EmailConfirmed { get; set; }
//
// Summary:
// Gets or sets the normalized email address for this user.
public virtual string NormalizedEmail { get; set; }
//
// Summary:
// Gets or sets the email address for this user.
[ProtectedPersonalData]
public virtual string Email { get; set; }
//
// Summary:
// Gets or sets the normalized user name for this user.
public virtual string NormalizedUserName { get; set; }
//
// Summary:
// Gets or sets the user name for this user.
[ProtectedPersonalData]
public virtual string UserName { get; set; }
//
// Summary:
// Gets or sets the primary key for this user.
[PersonalData]
public virtual TKey Id { get; set; }
//
// Summary:
// Gets or sets a flag indicating if the user could be locked out.
public virtual bool LockoutEnabled { get; set; }
//
// Summary:
// Gets or sets the number of failed login attempts for the current user.
public virtual int AccessFailedCount { get; set; }
//
// Summary:
// Returns the username for this user.
public override string ToString();
}
}
UPDATE : Just found out that I can not see Identity Tables in my database. That's another problem I guess.

Add role of a user in asp.net identity?

I want to assign a role from AspNetRoles to a user in AspNetUsers which the Id of both the role/user are to be stored in AspNetUserRoles.
[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)
{
var currentUser = UserManager.FindByName(user.UserName);
var roleresult = UserManager.AddToRole(currentUser.Id, "//Employee or Admin");
}
I have a dropdown menu from which SuperAdmin will select the Role of the user, it can be Admin or Employee. So I want to get the value of dropdown selected item. How can I do that?
Thanks.
In case when you want to have some Select/dropdown with available roles and create user with some selected role. You must to create Select/dropdown element for RegisterViewModel class property: SelectedRole with ability to select one element from a list of available roles.
How to populate DropDownList with roles you can find there.
Modified registration viewmodel to transfer SelectedRole value:
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[Display(Name = "Role")]
public string SelectedRole { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
Then during registration you have to use that selected role from your viewmodel and add your created user to that Role.
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var currentUser = UserManager.FindByName(user.UserName);
var roleresult = UserManager.AddToRole(currentUser.Id, model. SelectedRole);
...
}

How save the time of last log in using the SimpleMembership?

I need to save the time, when user loged in.
First I try to realize it in AccountController Login. But now I know, that isn't possible, because User.Id is -1.
Ok, and what can I do to save the time in my user table?
The next problem for me is, when do it. The informations I need to get the user are created after redirect. And the redirect can go everywhere.
An idea is, to get the time on which the ASPXAUTH cookie is set, but I don't know, how to get it.
Is there any solution?
If you need only last login time, you may receive that infrimation from SimpleMembership:
var User = Membership.GetUser("Username") // get user by name
DateTime LastActivityDate = User.LastActivityDate; // get last activity date and time
DateTime LastLogin = User.LastLoginDate; // Last login date and time
DateTime LastPasswordChange = User.LastPasswordChangedDate;
Just learning this stuff myself.
You need to extend the AccountModel, in AccountModel.cs
For example, I added Email to my UserProfile table generation.
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
public string Email { get; set; }
I also needed to add it to my RegisterModel further down the file
public class RegisterModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
The important part here is this new entry:
[Required]
[Display(Name = "Email Address")]
public string Email { get; set; }
Then I needed to add the Email information to the RegisterView, which is exactly the same as doing it with the other parts, since it is now part of the model.
One of the most important parts comes when you're saving the information back into the database. In my Register Action, I have the WebSecurity.CreateUserAndAccount method which was already defined. It requires tweaks however, to be able to add EXTRA information to the database with this method.
Thankfully one of the overloads allows passing in a dictionary to the extra data.
WebSecurity.CreateUserAndAccount(model.UserName, model.Password, new { Email = model.Email }, false);
new { Email = model.Email }
If you don't already have the column added in your database, I don't think it will pick it up. You need to make sure that column exists, so get it set up before hand.

SimpleMembership - Add email to UserProfile - System.Data.SqlClient.SqlException: Invalid column name "Email" error

I'm fairly sure I have followed all the steps, but seem to have missed something. Using simplemembership in MVC4 app. Add Email to UserProfile table and in the Register and UserProfile models, added it to the Register method, but still getting the error. Here is some code:
Models:
public class UserProfile
{
public int UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
public class RegisterModel
{
[Display(Name = "Email Address")]
[StringLength(20)]
// [Required]
public string Email { get; set; }
[Display(Name = "Date of Birth")]
// [Required]
public DateTime DOB { get; set; }
[Required]
[System.Web.Mvc.Remote("VerifyUserExists", "Account", ErrorMessage="That Username is already taken.")]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
Controller:
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
WebSecurity.CreateUserAndAccount(model.UserName, model.Password, new { Email = model.Email });
WebSecurity.Login(model.UserName, model.Password);
return RedirectToAction("Index", "Home");
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
}
}
I am not trying to use the email address as the login, just want to grab it during the registration step so I can send an auto confirm email.
I have tried with the UserProfile table included in the EF model, and with it out, no difference. I have confirmed the table in the DB has a Email column.
If you're using the default connection, open the database by clicking View -> Server Explorer then expand the DefaultConnection. Under Tables you will see the UserProfile table. Add your columns to the table first and update the database, then add your extra fields to the class.
I'm guessing that the Email property (in your UserProfile class) was somthing you added after you already executed the application for the first time , so if the table already exist befor you changed your model and added the Email property, It might be the cause for the exception.
as you mentioned yourself in one of your comments:
If I remove the section , new{ Email etc ' from the Register method in the controller, it pass fine
To fix that, I think you need to do somthing like that in your DbContext deriven class.(assuming you use code first approach):
public class myDbContext: DbContext
{
public myDbContext()
: base("DefaultConnection")
{
Database.SetInitializer<myDbContext>(new DropCreateDatabaseIfModelChanges<myDbContext>());
}
public DbSet<UserProfile> UserProfiles { get; set; }
}
The default setting is CreateDatabaseIfNotExists ,so if your UserProfile table was already exist, it didn't create it again and didn't find your new Email property (It was in your model but not in the database table);