I am new at asp.net Identity 2.0 and I want to show my FullName in Razor view page instade of Username.
So I added new property to IdentityUser.
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName => $"{FirstName} {LastName}";
}
Default AccountController contains a Login Method:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
var result = await SignInManager.PasswordSignInAsync(model.Email, 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 edited this login method
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
//-------------------------------------------------------
if (result == SignInStatus.Success)
{
var user = await UserManager.FindByEmailAsync(model.Email);
if (user != null)
{
await UserManager.AddClaimAsync(user.Id, new Claim("FullName", user.FullName));
}
}
//--------------------------------------------------------
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);
}
}
And I created an extention method to read FullName from Razor view:
public static class IdentityExtensions
{
public static string GetFullName(this IIdentity identity)
{
var claim = ((ClaimsIdentity) identity);
return claim.FindFirst("FullName");
}
}
But FullName allways coming Null
<ul class="nav navbar-nav navbar-right">
<li>
#Html.ActionLink("Hello " + User.Identity.GetFullName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
</li>
<li>Log off</li>
</ul>
The way you are trying to add the claim, you are creating an entry to database, in UserClaims table. If you want to do this then you have to add the claim (await UserManager.AddClaimAsync(user.Id, new Claim("FullName", user.FullName));) before PasswordSignInAsync and in my opinion not in Login action. Its better inside the action where you add and update the User's FirstName and LastName.
An other way is to add this data when ClaimsIdentity is generated on login. Inside your custom IdentityUser class there is a GenerateUserIdentityAsync method where you can simply:
public class ApplicationUser : IdentityUser
{
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
userIdentity.AddClaim(new Claim("LastName", $"{FirstName} {LastName}"));
return userIdentity;
}
}
Related
I'm currently customizing the registration page to pass in a companyID during registration. I'm fairly new to MVC best practices so if this isn't the most ideal approach please let me know. I have already modified the IdentityModel to accommodate the CompanyID property.
public class ApplicationUser : IdentityUser
{
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;
}
public Company Company { get; set; }
public int CompanyId { get; set; }
}
Currently I'm modifying the default registration page as a test.
Observed Behavior: The correct CompanyID is being grabbed correctly via the lambda expression. It fails to pass the viewModel to the async RegisterController.
Since it fails to pass the viewModel from the other Register action it fails to assign the CompanyID and throws a foreign key error.
// GET: /Account/Register
[AllowAnonymous]
public ActionResult Register()
{
var viewModel = new RegisterViewModel
{
CompanyID = _context.Companies.First(c => c.CompanyName == "Company2").Id
};
return View("Register", viewModel);
}
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, CompanyId = model.CompanyID };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771
// Send an email with this link
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Any recommendations is appreciated
You should add a field for the company id in the view.
In Register.cshtml, add:
<input type="hidden" name="CompanyId" value="#Model.CompanyId" />
Or, using the built-in HTML helper:
#Html.HiddenFor(m => m.CompanyId)
I am using this code for login. How can I find a user role when user login?
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.Username);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
ViewBag.errorMessage = "You must have a confirmed email to log on.";
return View("Error");
}
}
var result = await SignInManager.PasswordSignInAsync(model.Username, 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);
}
}
user.Roles will fetch list of Roles user belong to. Based on your requirement you can do something like below
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.Username);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
ViewBag.errorMessage = "You must have a confirmed email to log on.";
return View("Error");
}
}
var result = await SignInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
if(await UserManager.IsInRoleAsync(user.Id,"Admin")) //<= Checking Role and redirecting accordingly.
return RedirectToAction("Index", "Admin");
else
return RedirectToAction("Index", "User");
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);
}
}
Based on our discussion if you want to fetch all the roles from database you need to do below
Add ApplicationRoleManager class to your IdentityConfig.cs as below
public class ApplicationRoleManager : RoleManager<IdentityRole>
{
public ApplicationRoleManager(IRoleStore<IdentityRole, string> store)
: base(store)
{
}
public static ApplicationRoleManager Create(IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
{
var manager = new ApplicationRoleManager(new RoleStore<IdentityRole>(context.Get<ApplicationDbContext>()));
return manager;
}
}
Assign RoleManager to Owin Context, so add below to starup.auth.cs
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
//other code here
}
In AccountController.cs add a property
private ApplicationRoleManager _roleManager;
public ApplicationRoleManager RoleManager
{
get
{
return _roleManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationRoleManager>();
}
private set
{
_roleManager = value;
}
}
Pass it in the Constructor
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager,ApplicationRoleManager roleManager )
{
UserManager = userManager;
SignInManager = signInManager;
RoleManager = roleManager;
}
Once you are done with this you can fetch list of all roles by using
var roles = RoleManager.Roles;
You can use this as per your requirement.
I'm having a bit of an issue.
I am finishing work on my first mvc app and I'm using the default Login and Registration Actions.They work, but when I seed the db with initial data I add an administrator user(the user is added to the database correctly). When I try to login as this user - I cannot pass the authentication, I get an error saying "wrong password"
Any help would be strongly appreciated.(I really have no clue)
I am using mvc 5,identity 3.0, entityframework 6.
My account controller actions are
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
// if (ModelState.IsValid){
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation(1, "User logged in.");
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning(2, "User account locked out.");
return View("Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
// }
// If we got this far, something failed, redisplay form
// return View(model);
}
//
// GET: /Account/Register
[HttpGet]
[AllowAnonymous]
public IActionResult Register()
{
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser {UserName = model.UserName, Name = model.Name, Email = model.Email,BirthDate = model.BirthDate, LastName = model.Name };
PasswordHasher<ApplicationUser> a = new PasswordHasher<ApplicationUser>();
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{ await_signInManager.SignInAsync(user,isPersistent:false);
await _userManager.AddToRoleAsync(user, "User");
_logger.LogInformation(3, "User created a new account with password.");
return RedirectToAction(nameof(HomeController.Index), "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
And this is my data seed method (with class). it is called in the StartupCS after setting the routing.
public static async void Initialize(IServiceProvider serviceProvider)
{
var context = serviceProvider.GetService<ApplicationDbContext>();
bool need= false;
try
{
if (context.Users.Count() == 0)
need = true;
}
catch (Exception)
{ }
if (need)
{
string[] roles = new string[] { "Administrator", "User" };
foreach (string role in roles)
{
var roleStore = new RoleStore<IdentityRole>(context);
if (!context.Roles.Any(r => r.Name == role))
{
await roleStore.CreateAsync(new IdentityRole(role));
}
}
await context.SaveChangesAsync();
var user = new ApplicationUser
{
Name = "Admin",
LastName = "Admin",
Email = "admin#gmail.com",
NormalizedEmail = "ADMIN#GMAIL.COM",
UserName = "Owner",
NormalizedUserName = "OWNER",
PhoneNumber = "+923366633352",
};
var password = new PasswordHasher<ApplicationUser>();
var hashed = password.HashPassword(user, "secret");
user.PasswordHash = hashed;
var userStore = new UserStore<ApplicationUser>(context);
var result = userStore.CreateAsync(user);
UserManager<ApplicationUser> _userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();
//If I try this way - it keeeps on awaiting.
// var result =await _userManager.CreateAsync(user, "lol");
//the role seems to be working fine
var result2 = await _userManager.AddToRoleAsync(user, "Administrator");
}
}
}
Does anyone know how to enable a user to change username/email with ASP.NET identity with email confirmation? There's plenty of examples on how to change the password but I can't find anything on this.
Update Dec 2017 Some good points have been raised in comments:
Better have a separate field for new email while it is getting confirmed - in cases when user have entered incorrect email. Wait till the new email is confirmed, then make it the primary email. See very detailed answer from Chris_ below.
Also there could be a case when account with that email already exist - make sure you check for that too, otherwise there can be trouble.
This is a very basic solution that does not cover all possible combinations, so use your judgment and make sure you read through the comments - very good points have been raised there.
// get user object from the storage
var user = await userManager.FindByIdAsync(userId);
// change username and email
user.Username = "NewUsername";
user.Email = "New#email.com";
// Persiste the changes
await userManager.UpdateAsync(user);
// generage email confirmation code
var emailConfirmationCode = await userManager.GenerateEmailConfirmationTokenAsync(user.Id);
// generate url for page where you can confirm the email
var callbackurl= "http://example.com/ConfirmEmail";
// append userId and confirmation code as parameters to the url
callbackurl += String.Format("?userId={0}&code={1}", user.Id, HttpUtility.UrlEncode(emailConfirmationCode));
var htmlContent = String.Format(
#"Thank you for updating your email. Please confirm the email by clicking this link:
<br><a href='{0}'>Confirm new email</a>",
callbackurl);
// send email to the user with the confirmation link
await userManager.SendEmailAsync(user.Id, subject: "Email confirmation", body: htmlContent);
// then this is the action to confirm the email on the user
// link in the email should be pointing here
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
var confirmResult = await userManager.ConfirmEmailAsync(userId, code);
return RedirectToAction("Index");
}
Trailmax got most of it right, but as the comments pointed out, the user would be essentially stranded if they were to mess up their new email address when updating.
To address this, it is necessary to add additional properties to your user class and modify the login. (Note: this answer will be addressing it via an MVC 5 project)
Here's where I took it:
1. Modify your User object
First, let's update the Application User to add the additional field we'll need. You'll add this in the IdentiyModel.cs file in your Models folder:
public class ApplicationUser : IdentityUser
{
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;
}
[MaxLength(256)]
public string UnConfirmedEmail { get; set; }//this is what we add
}
If you want to see a more in depth example of that being done, check out this here http://blog.falafel.com/customize-mvc-5-application-users-using-asp-net-identity-2-0/ (that is the example I used)
Also, it doesn't mention it in the linked article, but you'll want to update your AspNetUsers table as well:
ALTER TABLE dbo.AspNetUsers
ADD [UnConfirmedEmail] NVARCHAR(256) NULL;
2. Update your login
Now we need to make sure our login is checking the old email confirmation as well so that things can be "in limbo" while we wait for the user to confirm this new email:
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var allowPassOnEmailVerfication = false;
var user = await UserManager.FindByEmailAsync(model.Email);
if (user != null)
{
if (!string.IsNullOrWhiteSpace(user.UnConfirmedEmail))
{
allowPassOnEmailVerfication = true;
}
}
// This now counts login failures towards account lockout
// To enable password failures to trigger account lockout, I changed to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return allowPassOnEmailVerfication ? RedirectToLocal(returnUrl) : RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
That's it...you are essentially done! However, I always get annoyed by half answers that don't walk you past potential traps you'll hit later on, so let's continue our adventure, shall we?
3. Update your Manage/Index
In our index.cshtml, let's add a new section for email. Before we get there though, let's go add the field we need in ManageViewmodel.cs
public class IndexViewModel
{
public bool HasPassword { get; set; }
public IList<UserLoginInfo> Logins { get; set; }
public string PhoneNumber { get; set; }
public bool TwoFactor { get; set; }
public bool BrowserRemembered { get; set; }
public string ConfirmedEmail { get; set; } //add this
public string UnConfirmedEmail { get; set; } //and this
}
Jump into the index action in our Manage controller to add that to our viewmodel:
var userId = User.Identity.GetUserId();
var currentUser = await UserManager.FindByIdAsync(userId);
var unConfirmedEmail = "";
if (!String.IsNullOrWhiteSpace(currentUser.UnConfirmedEmail))
{
unConfirmedEmail = currentUser.UnConfirmedEmail;
}
var model = new IndexViewModel
{
HasPassword = HasPassword(),
PhoneNumber = await UserManager.GetPhoneNumberAsync(userId),
TwoFactor = await UserManager.GetTwoFactorEnabledAsync(userId),
Logins = await UserManager.GetLoginsAsync(userId),
BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(userId),
ConfirmedEmail = currentUser.Email,
UnConfirmedEmail = unConfirmedEmail
};
Finally for this section we can update our index to allow us to manage this new email option:
<dt>Email:</dt>
<dd>
#Model.ConfirmedEmail
#if (!String.IsNullOrWhiteSpace(Model.UnConfirmedEmail))
{
<em> - Unconfirmed: #Model.UnConfirmedEmail </em> #Html.ActionLink("Cancel", "CancelUnconfirmedEmail",new {email=Model.ConfirmedEmail})
}
else
{
#Html.ActionLink("Change Email", "ChangeEmail")
}
</dd>
4. Add those new modifications
First, let's add ChangeEmail:
View Model:
public class ChangeEmailViewModel
{
public string ConfirmedEmail { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
[DataType(DataType.EmailAddress)]
public string UnConfirmedEmail { get; set; }
}
Get Action:
public ActionResult ChangeEmail()
{
var user = UserManager.FindById(User.Identity.GetUserId());
var model = new ChangeEmailViewModel()
{
ConfirmedEmail = user.Email
};
return View(model);
}
View:
#model ProjectName.Models.ChangeEmailViewModel
#{
ViewBag.Title = "Change Email";
}
<h2>#ViewBag.Title.</h2>
#using (Html.BeginForm("ChangeEmail", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>New Email Address:</h4>
<hr />
#Html.ValidationSummary("", new { #class = "text-danger" })
#Html.HiddenFor(m=>m.ConfirmedEmail)
<div class="form-group">
#Html.LabelFor(m => m.UnConfirmedEmail, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.UnConfirmedEmail, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Email Link" />
</div>
</div>
}
HttpPost Action:
[HttpPost]
public async Task<ActionResult> ChangeEmail(ChangeEmailViewModel model)
{
if (!ModelState.IsValid)
{
return RedirectToAction("ChangeEmail", "Manage");
}
var user = await UserManager.FindByEmailAsync(model.ConfirmedEmail);
var userId = user.Id;
if (user != null)
{
//doing a quick swap so we can send the appropriate confirmation email
user.UnConfirmedEmail = user.Email;
user.Email = model.UnConfirmedEmail;
user.EmailConfirmed = false;
var result = await UserManager.UpdateAsync(user);
if (result.Succeeded)
{
string callbackUrl =
await SendEmailConfirmationTokenAsync(userId, "Confirm your new email");
var tempUnconfirmed = user.Email;
user.Email = user.UnConfirmedEmail;
user.UnConfirmedEmail = tempUnconfirmed;
result = await UserManager.UpdateAsync(user);
callbackUrl = await SendEmailConfirmationWarningAsync(userId, "You email has been updated to: "+user.UnConfirmedEmail);
}
}
return RedirectToAction("Index","Manage");
}
Now add that warning:
private async Task<string> SendEmailConfirmationWarningAsync(string userID, string subject)
{
string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = userID, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(userID, subject,
"Please confirm your account by clicking here");
return callbackUrl;
}
And now finally, we can put in the cancellation of the new email address:
public async Task<ActionResult> CancelUnconfirmedEmail(string emailOrUserId)
{
var user = await UserManager.FindByEmailAsync(emailOrUserId);
if (user == null)
{
user = await UserManager.FindByIdAsync(emailOrUserId);
if (user != null)
{
user.UnConfirmedEmail = "";
user.EmailConfirmed = true;
var result = await UserManager.UpdateAsync(user);
}
}
else
{
user.UnConfirmedEmail = "";
user.EmailConfirmed = true;
var result = await UserManager.UpdateAsync(user);
}
return RedirectToAction("Index", "Manage");
}
5. Update ConfirmEmail (the very very last step)
After all this back and forth we can now confirm the new email, which means we should remove the old email at the same time.
var result = UserManager.ConfirmEmail(userId, code);
if (result.Succeeded)
{
var user = UserManager.FindById(userId);
if (!string.IsNullOrWhiteSpace(user.UnConfirmedEmail))
{
user.Email = user.UnConfirmedEmail;
user.UserName = user.UnConfirmedEmail;
user.UnConfirmedEmail = "";
UserManager.Update(user);
}
}
Haven't looked at ChangeEmailOnIdentity2.0ASPNET yet, but couldn't you just take advantage of the fact that the UserName and Email values typically match? This allows you to change the Email column upon request and then UserName upon confirmation.
These two controllers seem to work for me:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ChangeUserName(LoginViewModel model)
{
IdentityResult result = new IdentityResult();
try
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
SignInStatus verify = await SignInManager.PasswordSignInAsync(user.UserName, model.Password, false, false);
if (verify != SignInStatus.Success)
{
ModelState.AddModelError("Password", "Incorrect password.");
}
else
{
if (model.Email != user.Email)
{
user.Email = model.Email;
user.EmailConfirmed = false;
// Persist the changes
result = await UserManager.UpdateAsync(user);
if (result.Succeeded)
{
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Confirm your updated email", "Please confirm your email address by clicking this");
return RedirectToAction("Index", new { Message = ManageMessageId.ChangeUserNamePending });
}
}
else
{
ModelState.AddModelError("Email", "Address specified matches current setting.");
}
}
}
}
catch (Exception ex)
{
result.Errors.Append(ex.Message);
}
AddErrors(result);
return View(model);
}
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
var result = await UserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(userId);
if (user.Email != user.UserName)
{
// Set the message to the current values before changing
String message = $"Your email user name has been changed from {user.UserName} to {user.Email} now.";
user.UserName = user.Email;
result = await UserManager.UpdateAsync(user);
if (result.Succeeded)
{
ViewBag.Message = message;
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
}
else
{
result.Errors.Append("Could not modify your user name.");
AddErrors(result);
return View("Error");
}
}
return View("ConfirmEmail");
}
else
{
return View("Error");
}
}
In case anyone is looking for a solution with Asp.Net Core:
Here things are much more simple, see this post on SO
AspNet Core Generate and Change Email Address
I followed the steps of Jonathan to a brand new ASP.NET project to test the changes and worked like a charm. This is the link to the repository
I am trying to add a user to a role using the following code but the UserManager always returns a NULLReferenceException. Any tips for how to get this to work. I feel like it should not be that hard.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RoleAddToUser(string UserName, string RoleName)
{
ApplicationUser user = cd.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var account = new AccountController();
account.UserManager.AddToRole(user.Id, RoleName);
ViewBag.ResultMessage = "Role created successfully !";
// prepopulat roles for the view dropdown
var list = cd.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
ViewBag.Roles = list;
return View("Index");
}
Here is my account controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Owin;
using LaCeibaNetv4.Models;
namespace LaCeibaNetv4.Controllers
{
[Authorize]
public class AccountController : Controller
{
private ApplicationUserManager _userManager;
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager)
{
UserManager = userManager;
}
public ApplicationUserManager UserManager {
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
//
// GET: /Account/Login
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindAsync(model.Email, model.Password);
if (user != null)
{
await SignInAsync(user, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
//
// GET: /Account/Register
[AllowAnonymous]
public ActionResult Register()
{
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model, string passCode)
{
if (ModelState.IsValid && passCode == "Fury")
{
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
// Send an email with this link
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
//
// GET: /Account/ConfirmEmail
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
IdentityResult result = await UserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
{
return View("ConfirmEmail");
}
else
{
AddErrors(result);
return View();
}
}
//
// GET: /Account/ForgotPassword
[AllowAnonymous]
public ActionResult ForgotPassword()
{
return View();
}
//
// POST: /Account/ForgotPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
{
ModelState.AddModelError("", "The user either does not exist or is not confirmed.");
return View();
}
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
// Send an email with this link
// string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
// var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// await UserManager.SendEmailAsync(user.Id, "Reset Password", "Please reset your password by clicking here");
// return RedirectToAction("ForgotPasswordConfirmation", "Account");
}
// If we got this far, something failed, redisplay form
return View(model);
}
//
// GET: /Account/ForgotPasswordConfirmation
[AllowAnonymous]
public ActionResult ForgotPasswordConfirmation()
{
return View();
}
//
// GET: /Account/ResetPassword
[AllowAnonymous]
public ActionResult ResetPassword(string code)
{
if (code == null)
{
return View("Error");
}
return View();
}
//
// POST: /Account/ResetPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
ModelState.AddModelError("", "No user found.");
return View();
}
IdentityResult result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
else
{
AddErrors(result);
return View();
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
//
// GET: /Account/ResetPasswordConfirmation
[AllowAnonymous]
public ActionResult ResetPasswordConfirmation()
{
return View();
}
//
// POST: /Account/Disassociate
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Disassociate(string loginProvider, string providerKey)
{
ManageMessageId? message = null;
IdentityResult result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(), new UserLoginInfo(loginProvider, providerKey));
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
await SignInAsync(user, isPersistent: false);
message = ManageMessageId.RemoveLoginSuccess;
}
else
{
message = ManageMessageId.Error;
}
return RedirectToAction("Manage", new { Message = message });
}
//
// GET: /Account/Manage
public ActionResult Manage(ManageMessageId? message)
{
ViewBag.StatusMessage =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
: message == ManageMessageId.Error ? "An error has occurred."
: "";
ViewBag.HasLocalPassword = HasPassword();
ViewBag.ReturnUrl = Url.Action("Manage");
return View();
}
//
// POST: /Account/Manage
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Manage(ManageUserViewModel model)
{
bool hasPassword = HasPassword();
ViewBag.HasLocalPassword = hasPassword;
ViewBag.ReturnUrl = Url.Action("Manage");
if (hasPassword)
{
if (ModelState.IsValid)
{
IdentityResult result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Manage", new { Message = ManageMessageId.ChangePasswordSuccess });
}
else
{
AddErrors(result);
}
}
}
else
{
// User does not have a 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)
{
IdentityResult result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
if (result.Succeeded)
{
return RedirectToAction("Manage", new { Message = ManageMessageId.SetPasswordSuccess });
}
else
{
AddErrors(result);
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
//
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
//
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var user = await UserManager.FindAsync(loginInfo.Login);
if (user != null)
{
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
else
{
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
//
// POST: /Account/LinkLogin
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LinkLogin(string provider)
{
// Request a redirect to the external login provider to link a login for the current user
return new ChallengeResult(provider, Url.Action("LinkLoginCallback", "Account"), User.Identity.GetUserId());
}
//
// GET: /Account/LinkLoginCallback
public async Task<ActionResult> LinkLoginCallback()
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
if (loginInfo == null)
{
return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
}
IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
if (result.Succeeded)
{
return RedirectToAction("Manage");
}
return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
}
//
// POST: /Account/ExternalLoginConfirmation
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Manage");
}
if (ModelState.IsValid)
{
// Get the information about the user from the external login provider
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null)
{
return View("ExternalLoginFailure");
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
// Send an email with this link
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// SendEmail(user.Email, callbackUrl, "Confirm your account", "Please confirm your account by clicking this link");
return RedirectToLocal(returnUrl);
}
}
AddErrors(result);
}
ViewBag.ReturnUrl = returnUrl;
return View(model);
}
//
// POST: /Account/LogOff
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
return RedirectToAction("Index", "Home");
}
//
// GET: /Account/ExternalLoginFailure
[AllowAnonymous]
public ActionResult ExternalLoginFailure()
{
return View();
}
[ChildActionOnly]
public ActionResult RemoveAccountList()
{
var linkedAccounts = UserManager.GetLogins(User.Identity.GetUserId());
ViewBag.ShowRemoveButton = HasPassword() || linkedAccounts.Count > 1;
return (ActionResult)PartialView("_RemoveAccountPartial", linkedAccounts);
}
protected override void Dispose(bool disposing)
{
if (disposing && UserManager != null)
{
UserManager.Dispose();
UserManager = null;
}
base.Dispose(disposing);
}
#region Helpers
// Used for XSRF protection when adding external logins
private const string XsrfKey = "XsrfId";
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, await user.GenerateUserIdentityAsync(UserManager));
}
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
private bool HasPassword()
{
var user = UserManager.FindById(User.Identity.GetUserId());
if (user != null)
{
return user.PasswordHash != null;
}
return false;
}
private void SendEmail(string email, string callbackUrl, string subject, string message)
{
// For information on sending mail, please visit http://go.microsoft.com/fwlink/?LinkID=320771
}
public enum ManageMessageId
{
ChangePasswordSuccess,
SetPasswordSuccess,
RemoveLoginSuccess,
Error
}
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
private 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);
}
}
#endregion
}
}
If you are getting an Sql exception its.more likely the fields are returning a null value on post. Set a BP on your POST event and step through it and ensure your values are not null...
Also what's RoleAddToUser looks to me like your using identity. With identity there id a built in AddToRole so if that's a custom function am not really sure why ? You need to say what exactly is null. But my guess is your not sending the correct fields back.
Also if its custom just so you can do dropdown list of roles on my programme I just make the dropdown similar to what you did there but I do it in the login controller and ensure they cant select Admin.
I think you might be getting error on the following line :
account.UserManager.AddToRole(user.Id, RoleName);
at user.Id
You must check :
if (user != null)
then add User to Role.