asp.net identity userName is unique? - c#

I was reading about user Identity in Microsoft and trying to apply them in my MVC5 app.
Up to my knowledge the Id is the key, while the userName is not key and the definition says that it can be null,
so I was asking myself... why in the MVC5 project template, when you enter an already existing userName you will receive an Error message ??
I tried to reach to the userName validation but I couldn't.
Here is the database definition:
CREATE TABLE [dbo].[AspNetUsers] (
[Id] NVARCHAR (128) NOT NULL,
[UserName] NVARCHAR (MAX) NULL,
and here is the IdentityUser definition, notice (no validation):
namespace Microsoft.AspNet.Identity.EntityFramework
{
public class IdentityUser : IUser
{
public IdentityUser();
public IdentityUser(string userName);
public virtual ICollection<IdentityUserClaim> Claims { get; }
public virtual string Id { get; set; }
public virtual ICollection<IdentityUserLogin> Logins { get; }
public virtual string PasswordHash { get; set; }
public virtual ICollection<IdentityUserRole> Roles { get; }
public virtual string SecurityStamp { get; set; }
public virtual string UserName { get; set; }
}
}
and on registration, the UserManager.CreateAsync method is called, here is the definition:
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);
}
and this is the last thing I reach regarding CreateAsync:
public virtual Task<IdentityResult> CreateAsync(TUser user, string password);
I don't see validation anywhere in the code, however it won't allow you to enter an existing userName.
I think understanding how does this works will improve my experience with the Identity concept of asp.net and will improve my code.
Any guidance is highly appreciated

This happens in IdentityDbContext<TUser>, which your ApplicationDbContext probably inherits from. It overrides DbContext's ValidateEntity method to do the check. See this decompiled code:
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
if ((entityEntry != null) && (entityEntry.State == EntityState.Added))
{
TUser user = entityEntry.Entity as TUser;
if ((user != null) && this.Users.Any<TUser>(u => string.Equals(u.UserName, user.UserName)))
{
return new DbEntityValidationResult(entityEntry, new List<DbValidationError>()) { ValidationErrors = { new DbValidationError("User", string.Format(CultureInfo.CurrentCulture, IdentityResources.DuplicateUserName, new object[] { user.UserName })) } };
}
IdentityRole role = entityEntry.Entity as IdentityRole;
if ((role != null) && this.Roles.Any<IdentityRole>(r => string.Equals(r.Name, role.Name)))
{
return new DbEntityValidationResult(entityEntry, new List<DbValidationError>()) { ValidationErrors = { new DbValidationError("Role", string.Format(CultureInfo.CurrentCulture, IdentityResources.RoleAlreadyExists, new object[] { role.Name })) } };
}
}
return base.ValidateEntity(entityEntry, items);
}
If you don't want this behavior you can inherit from DbContext directly.

When I'm looking at the example of ASP.NET Identity (https://www.nuget.org/packages/Microsoft.AspNet.Identity.Samples) I noticed that they use a UserValidator which is default set to RequireUniqueEmail = true;
The example uses the following code to set the RequireUniqueEmail property to true.
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
return manager;
}
Perhaps this is the reason that the username is unique in your MVC application.
Try setting the property to false!?

Related

Asp.Net Core Web API - Error CS0747 Invalid initializer member declarator

I have this code in ASP.NET Core Web API:
DTO:
public class AuthRequest
{
public string Email { get; set; }
public string Password { get; set; }
}
public class AuthResponse
{
public string Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Token { get; set; }
}
Services:
public interface IAuthService
{
Task<AuthResponse> Login(AuthRequest request);
}
public async Task<AuthResponse> Login(AuthRequest request)
{
var user = await _userManager.FindByEmailAsync(request.Email);
if (user == null)
{
throw new Exception($"User with {request.Email} not found.");
}
var result = await _signInManager.PasswordSignInAsync(user.UserName, request.Password, false, lockoutOnFailure: false);
if (!result.Succeeded)
{
throw new Exception($"Credentials for '{request.Email} aren't valid'.");
}
JwtSecurityToken jwtSecurityToken = await GenerateToken(user);
var roles = await _userManager.GetRolesAsync(user);
AuthResponse response = new AuthResponse
{
Id = user.Id,
Token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken),
Email = user.Email,
UserName = user.UserName,
roles,
};
return response;
}
From the code above, I want to include roles of the logged in user in the AuthResponse. So I added, roles
But I got this error:
Error CS0747 Invalid initializer member declarator
roles is highlighted.
How do I resolve this?
Thanks
Notice how this property initialization:
roles,
differs from the rest:
Id = user.Id,
Token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken),
Email = user.Email,
UserName = user.UserName,
You need to specify which property is being set. For example, is there a Roles property (which matches the type)? If so, it would be something like this:
Roles = roles,
Basically, whatever property you're trying to initialize in the resulting object, you need to indicate the name of that property in its initialization.

How to check phone number is unique in AspNetUsers table when using ASP.NET Web Api

I have created an Asp.Net Web Api project and used Individual user accounts.
When I am adding users to the table the default system automatically checks if the email address supplied already exists in the table, if so a bad request is thrown otherwise the user can be submitted.
How can I also check if the Phone Number is unique and hasn't already been submitted to the table?
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
**using (ApplicationDbContext db = new ApplicationDbContext())
{
var foundPhoneNumber = await db.Users.FirstOrDefaultAsync(x => x.PhoneNumber.Equals(model.PhoneNumber));
if (foundPhoneNumber != null)
{
return BadRequest("Phone number already exists");
}
}**
var user = new ApplicationUser()
{
UserName = model.Email,
Email = model.Email,
PhoneNumber = model.PhoneNumber,
FirstName = model.FirstName,
LastName = model.LastName,
MemberNumber = model.MemberNumber,
CarReg = model.CarReg
};
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
I have queried the database to check if there is a Phone Number with the same number. This works, but is there a better way to do this?
Modify your ApplicationUser call and add the following attributes.
public class ApplicationUser : IdentityUser
{
[MaxLength(17)]
[IsUnique]
public string PhoneNumber { get; set; }
}
You can override ValidateEntity method in ApplicationUserDbContext class, it will trigger on SaveChanges method.
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
if (entityEntry != null && entityEntry.State == EntityState.Added)
{
var errors = new List<DbValidationError>();
////User Validation
if (entityEntry.Entity is ApplicationUser user)
{
if (this.Users.Any(u => string.Equals(u.PhoneNumber, user.PhoneNumber)))
{
errors.Add(new DbValidationError("User",
string.Format($"Phonenumber {user.PhoneNumber} is already taken")));
}
}
if (errors.Any())
{
return new DbEntityValidationResult(entityEntry, errors);
}
}
return new DbEntityValidationResult(entityEntry, new List<DbValidationError>());
}
Validation can be added via a custom ValidationAttribute that you add to the PhoneNumber property on you model. Here is a simple example:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class NotABananaAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var inputValue = value as string;
var isValid = true;
if (!string.IsNullOrEmpty(inputValue))
{
isValid = inputValue.ToUpperInvariant() != "BANANA";
}
return isValid;
}
}
And its used liked this...
public class Model
{
[NotABanana(ErrorMessage = "Bananas are not allowed.")]
public string FavoriteFruit { get; set; }
}
Example sourced from:
https://riptutorial.com/csharp/example/18486/creating-a-custom-validation-attribute

How to fix Db after invalid column name, and migration failed

I added properties to the ApplicationUser.cs (which I've done before) and everything exploded. This class extends IdentityUser. When I comment out the properties everything appears to work fine. What did I do wrong?
ApplicationUser.cs
public class ApplicationUser : IdentityUser
{
public string LGID { get; } = "";
public string CoIDs { get; } = "";
public string LGIDSuperUserName { get; set; } = "unknown"; //new
public bool IsSuperUser { get; set; } = false; //new
public ApplicationUser() { }
public ApplicationUser(ClaimsIdentity identity)
{
IEnumerable<Claim> claims = identity.Claims;
foreach (Claim c in claims)
{
if (c.Type == "LGID")
LGID = c.Value;
if (c.Type == "CoIDs")
CoIDs = c.Value;
if (c.Type == "LGIDSuperUser")
LGIDSuperUserName = c.Value;
if (c.Type == "IsSuperUser")
IsSuperUser = c.Value.ToLower()=="yes";
}
}
}
More info if needed. Not sure whats relevant here
When I go to login to my site I first got an error that suggested I needed to migrate the DB. In NuGet Package Manager I ran:
Add-Migration ApplicationDbContext
Update-Database
Now the error message when I try to login says:
Invalid column name 'IsSuperUser'.
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
Here is my Migrations folder inside VisualStudio:

Get User Roles with ASP.net Identity and Web API

I am currently trying to get the given user's list of Roles and am having some trouble fitting this into the context we are using it in. I was able to get a list of all available roles with this API function earlier,
[HttpGet]
[Route("GetRoles")]
public async Task<ApiResponse<List<RoleViewModel>>> GetRoles()
{
try
{
//Get Roles
var roles = await (from r in _db.AspNetRoles
select new RoleViewModel { Id = r.Id, Name = r.Name}).ToListAsync();
return new ApiResponse<List<RoleViewModel>>{ Success = true, Result = roles };
}
catch(Exception ex)
{
return new ApiResponse<List<RoleViewModel>> { Success = false, Message = ex.Message };
}
}
But can't seem to figure out what I need to throw into this one to get a list of the roles for the user. We went with Entity Frameworks Code First from Existing Database approach and are pulling from those tables. Strangely though there is no AspNetUserRoles table since I guess it is just relating the two tables AspNetUsers and AspNetRoles. Anyway, here is the function in question,
[HttpGet]
[Route("GetUserRoles")]
public async Task<ApiResponse<List<RoleViewModel>>> GetUserRoles(string userName)
{
try
{
var userRoles = await (_db.AspNetUsers.FirstOrDefault(u => u.UserName == userName).AspNetRoles).ToListAsync();
}
catch (Exception ex)
{
return new ApiResponse<List<RoleViewModel>> { Success = false, Message = ex.Message };
}
}
The current error I am getting is that AspNetRole does not contain a definition for ToListAsync(). I think the async stuff is throwing me a little. And lastly here is the RoleViewModel for reference,
public class RoleViewModel
{
public string Id { get; set; }
[Required]
[StringLength(256)]
public string Name { get; set; }
}
And the ApiResponse class,
public class ApiResponse<TResult>
{
public bool Success { get; set; }
public string Message { get; set; }
public TResult Result { get; set; }
}
I feel like there should be a simple fix, but I just can't quite grasp what it is.
Just found the answer to my problem. The main thing I was missing was utilization of the User Manager which made things so much easier. Then I just had to fit things into the functions I had already defined. Here is the code.
[HttpGet]
[Route("GetUserRoles")]
public async Task<ApiResponse<List<RoleViewModel>>> GetUserRoles(string userName)
{
try
{
// Get the user in question
var aspUser = (from u in _db.AspNetUsers
where u.UserName == userName
select u).FirstOrDefaultAsync();
// Check if the user was found
if (aspUser == null)
{
throw new Exception("User was not found");
}
// Get the roles associated with that user
var userRoles = await UserManager.GetRolesAsync(aspUser.Result.Id.ToString());
// Setup a RoleViewModel list of roles and iterate through userRoles adding them to the list
List<RoleViewModel> roleList = new List<RoleViewModel>();
foreach (var u in userRoles)
{
var item = new RoleViewModel { Name = u };
roleList.Add(item);
}
return new ApiResponse<List<RoleViewModel>> { Success = true, Result = roleList };
}
catch (Exception ex)
{
return new ApiResponse<List<RoleViewModel>> { Success = false, Message = ex.Message };
}
}

Register user asp.net identity model error

I am creating a website using asp.net identity. I have created a data model based upon the default asp.net identity tables which is using the localdb. When I try to register a new user I get an error saying Bitev2.Models.AspNetUserLogin: : EntityType 'AspNetUserLogin' has no key defined. Define the key for this EntityType.
AspNetUserLogins: EntityType: EntitySet 'AspNetUserLogins' is based on type 'AspNetUserLogin' that has no keys defined. However the AspNetUserLogin model is auto generated and contains no key.
Any help would be greatly appreciated.
AspNetUserLogin model
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Bitev2.Models
{
using System;
using System.Collections.Generic;
public partial class AspNetUserLogin
{
public string UserId { get; set; }
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
public virtual AspNetUser AspNetUser { get; set; }
}
}
AspNetLogins table/model
As I said yesterday, Asp.Net Identity is something different!
I have created a data model based upon the default asp.net identity tables which is using the localdb.
You don't need this step to register a new User. The AspNetUserLogin table has an other purpose, the one consisting in saving external logins for the current user. So the user can login from Google, Facebook, etc.
To simply register a user, please drop AspNet**** tables from your model and write this code:
//GET
[AllowAnonymous]
public ActionResult RegisterNewUser()
{
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> RegisterNewUser(RegisterNewUserViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = userViewModel.Email,
Email = userViewModel.Email,
EmailConfirmed =true
};
var adminresult = await UserManager.CreateAsync(user, userViewModel.Password);
//Add User to the Roles
string[] selectedRoles=new string[]{"Developer","Tester","Robbot"};
if (adminresult.Succeeded)
{
if (selectedRoles != null)
{
var result = await UserManager.AddToRolesAsync(user.Id, selectedRoles);
if (!result.Succeeded)
{
ModelState.AddModelError("", result.Errors.First());
return View();
}
}
}
else
{
ModelState.AddModelError("", adminresult.Errors.First());
return View();
}
return RedirectToAction("Index");
}
return View();
}
To use the AspNetUserLogin, You need two methods or steps:
The First method is the one that will request a redirect to the external login provider ExternalLogin for example, and
The Second method/step you need is the one that will save the External Login in the AspNetUserLogin table. this, without having this table generated in your model. Let's call this method ExternalLoginConfirmation.
Need code?
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Manage");
}
if (ModelState.IsValid)
{
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null)
{
return View("ExternalLoginFailure");
}
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
//saving the External Login in the `AspNetUserLogin` table
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal("local url here");
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
ViewBag.ReturnUrl = returnUrl;
return View(model);
}
You'll need this class to!
internal class ChallengeResult : HttpUnauthorizedResult
{
public ChallengeResult(string provider, string redirectUri)
: this(provider, redirectUri, null)
{
}
public ChallengeResult(string provider, string redirectUri, string userId)
{
LoginProvider = provider;
RedirectUri = redirectUri;
UserId = userId;
}
public string LoginProvider { get; set; }
public string RedirectUri { get; set; }
public string UserId { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}
}
If you need more information or if you are facing issue with missing types, see this post
Hope this will help you...
Kind Regards!

Categories

Resources