ASP.NET MVC 5 how to delete a user and its related data in Identity 2.0 - asp.net-mvc

I'm following this article to delete a user in Identity 2.0
http://www.asp.net/mvc/tutorials/mvc-5/introduction/examining-the-details-and-delete-methods
However, I need to delete all related records in AspNetUserRoles first and then delete the user.
I found an example which is written in Identity 1.0 and some of methods used inside this example don't exist.
// POST: /Users/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(string id)
{
if (ModelState.IsValid)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var user = await context.Users.FindAsync(id);
var logins = user.Logins;
foreach (var login in logins)
{
context.UserLogins.Remove(login);
}
var rolesForUser = await IdentityManager.Roles.GetRolesForUserAsync(id, CancellationToken.None);
if (rolesForUser.Count() > 0)
{
foreach (var item in rolesForUser)
{
var result = await IdentityManager.Roles.RemoveUserFromRoleAsync(user.Id, item.Id, CancellationToken.None);
}
}
context.Users.Remove(user);
await context.SaveChangesAsync();
return RedirectToAction("Index");
}
else
{
return View();
}
}
I cannot find IdentityManager from anywhere, and context.Users doesn't have FindAsync() method either.
How can I properly delete a User and its related records in Identity 2.0?

I think the classes you're looking for are the UserManager and the RoleManager. In my opinion they are the better way instead of going against the context directly.
The UserManager defines a method RemoveFromRoleAsync which gives you the ability to remove the user (identified by his key) from a given role. It also defines several Find methods, such as FindAsync, FindByIdAsync, FindByNameAsync, or FindByEmailAsync. They all can be used to retrieve a user. To delete a user you should use the DeleteAsync method which accepts a user object as a parameter. To get the roles a user is member of Identity gives you the GetRolesAsync method where you pass in the ID of the user. Also I see that you're trying to remove a login from a user. For this purpose you should use the RemoveLoginAsync method.
All in all your code would look similar to the following one:
// POST: /Users/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(string id)
{
if (ModelState.IsValid)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var user = await _userManager.FindByIdAsync(id);
var logins = user.Logins;
var rolesForUser = await _userManager.GetRolesAsync(id);
using (var transaction = context.Database.BeginTransaction())
{
foreach (var login in logins.ToList())
{
await _userManager.RemoveLoginAsync(login.UserId, new UserLoginInfo(login.LoginProvider, login.ProviderKey));
}
if (rolesForUser.Count() > 0)
{
foreach (var item in rolesForUser.ToList())
{
// item should be the name of the role
var result = await _userManager.RemoveFromRoleAsync(user.Id, item);
}
}
await _userManager.DeleteAsync(user);
transaction.Commit();
}
return RedirectToAction("Index");
}
else
{
return View();
}
}
You'll need to adjust this snippet to your needs, because I don't have an idea how your IdentityUser implementation looks like. Remember to declare the UserManager as needed. An example how you could do this can be found when you create a new project in Visual Studio using Individual Accounts.

Update for ASP.NET Core 2.0 - hope this saves someone a bit of time
ApplicationDbContext context,
UserManager<ApplicationUser> userManager,
ApplicationUser user
var logins = await userManager.GetLoginsAsync(user);
var rolesForUser = await userManager.GetRolesAsync(user);
using (var transaction = context.Database.BeginTransaction())
{
IdentityResult result = IdentityResult.Success;
foreach (var login in logins)
{
result = await userManager.RemoveLoginAsync(user, login.LoginProvider, login.ProviderKey);
if (result != IdentityResult.Success)
break;
}
if (result == IdentityResult.Success)
{
foreach (var item in rolesForUser)
{
result = await userManager.RemoveFromRoleAsync(user, item);
if (result != IdentityResult.Success)
break;
}
}
if (result == IdentityResult.Success)
{
result = await userManager.DeleteAsync(user);
if (result == IdentityResult.Success)
transaction.Commit(); //only commit if user and all his logins/roles have been deleted
}
}

Brad's point about requiring #Html.AntiForgeryToken() in views is not necessary if you are using latest versions of ASP.NET - see AntiForgeryToken still required
Why not create a SQL trigger for AspNetUsers so deleting a user also deletes the corresponding records for user from AspNetUserRoles and AspNetUserLogins?
I need to invoke DeleteUser from a number of places so I added a static method to AccountController (see below). I'm still learning about MVC, so should be grateful for comments, in particular 1) use of IdentityResult as a return code 2) wisdom of extending AccountController in this way 3) approach for putting password (cleartext) into the Model to validate the action (see sample invocation).
public static async Task<IdentityResult> DeleteUserAccount(UserManager<ApplicationUser> userManager,
string userEmail, ApplicationDbContext context)
{
IdentityResult rc = new IdentityResult();
if ((userManager != null) && (userEmail != null) && (context != null) )
{
var user = await userManager.FindByEmailAsync(userEmail);
var logins = user.Logins;
var rolesForUser = await userManager.GetRolesAsync(user);
using (var transaction = context.Database.BeginTransaction())
{
foreach (var login in logins.ToList())
{
await userManager.RemoveLoginAsync(user, login.LoginProvider, login.ProviderKey);
}
if (rolesForUser.Count() > 0)
{
foreach (var item in rolesForUser.ToList())
{
// item should be the name of the role
var result = await userManager.RemoveFromRoleAsync(user, item);
}
}
rc = await userManager.DeleteAsync(user);
transaction.Commit();
}
}
return rc;
}
Sample invocation - form passes the user's password (cleartext) in Model:
// POST: /Manage/DeleteUser
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteUser(DeleteUserViewModel account)
{
var user = await GetCurrentUserAsync();
if ((user != null) && (user.PasswordHash != null) && (account != null) && (account.Password != null))
{
var hasher = new Microsoft.AspNetCore.Identity.PasswordHasher<ApplicationUser>();
if(hasher.VerifyHashedPassword(user,user.PasswordHash, account.Password) != PasswordVerificationResult.Failed)
{
IdentityResult rc = await AccountController.DeleteUserAccount( _userManager, user.Email, _Dbcontext);
if (rc.Succeeded)
{
await _signInManager.SignOutAsync();
_logger.LogInformation(4, "User logged out.");
return RedirectToAction(nameof(HomeController.Index), "Home");
}
}
}
return View(account);
}

I was looking also for the answer but finally this is what work well for me, even its old post but it may help for someone.
// GET: Users/Delete/5
public ActionResult Delete(string id)
{
using (SqlConnection sqlCon = new SqlConnection(connectionString))
{
sqlCon.Open();
string query = "DELETE FROM AspNetUsers WHERE Id = #Id";
SqlCommand sqlCmd = new SqlCommand(query, sqlCon);
sqlCmd.Parameters.AddWithValue("#Id", id);
sqlCmd.ExecuteNonQuery();
}
return RedirectToAction("Index");
}
// POST: Users/Delete/5
[HttpPost]
public ActionResult Delete(string id, FormCollection collection)
{
try
{
// TODO: Add delete logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}

Related

.NET CORE 3.1, MVC Async method not updating DB

AI am just moving to ASYNC methods and trying to get my data to update. I can select just find so I know the repository is working.
Action
[HttpPost]
public async Task<IActionResult> EditTeam(EmployeeVm empVm)
{
if (!ModelState.IsValid)
{
ModelState.AddModelError("", _errorUpdateMsg);
}
else
{
if (await _teamRepository.UpdateEmployee(empVm.Employee))
{
return RedirectToAction("Index");
}
ModelState.AddModelError("", _errorUpdateMsg);
}
return View(empVm);
}
My Constructor in repo
public TeamRepository(EnvisionDbContext envisionDbContext)
{
_envisonDbContext = envisionDbContext;
}
Here is my Update that does not save
public async Task<bool> UpdateEmployee(Employee employee)
{
var result = await _envisonDbContext.Employees.FirstOrDefaultAsync<Employee>(e => e.Id == employee.Id);
if (result != null)
{
result.FirstName = employee.FirstName;
result.LastName = employee.LastName;
result.Phone = employee.Phone;
result.IsActive = employee.IsActive;
await _envisonDbContext.SaveChangesAsync();
return true;
}
return false;
}
Thanks in advance for the help.
UPDATED: If I add this, it works. Is this because the two await calls are disconnected?
result.IsActive = employee.IsActive;
_envisonDbContext.Entry(result).State = EntityState.Modified;
Seems like you forgot update-method before savingchanges
if (result != null)
{
result.FirstName = employee.FirstName;
result.LastName = employee.LastName;
result.Phone = employee.Phone;
result.IsActive = employee.IsActive;
_envisionDbContext.Update(result); //paste it before you save changes
await _envisonDbContext.SaveChangesAsync();
return true;
}

Token generated outside controller is too long and it's rejected by ConfirmEmail on Controller in MVC C#

I am updating my question as I have made some progress.
Thanks in advance for your support.
Question:
I am using GenerateEmailConfirmationTokenAsync to create a token outside the Controller (it's working fine), but somehow my token is longer than the ones created within the Controller using the GenerateEmailConfirmationTokenAsync and therefore the ConfirmEmail action rejects the token. (Error: Invalid Token).
I have tried Machinekey on webconfig, HttpUtility.UrlEncode, but I am still stuck. How to sort out the Invalid Token error on Controller ConfirmEmail?
Guys, can you help me please!
Thanks.
Here is my Code:
RegisterUser (outside Controller)
public async Task RegisterUserAsync()
{
var store = new UserStore<ApplicationUser>(db);
var UserManager = new ApplicationUserManager(store);
var query = from c in db.Customer
where !(from o in db.Users
select o.customer_pk)
.Contains(c.customer_pk)
select c;
var model = query.ToList();
if (query != null)
{
foreach (var item in model)
{
var user = new ApplicationUser { UserName = item.email, Email = item.email, customerId = item.customerId};
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id);
SmtpClient client = new SmtpClient();
MailMessage message = new MailMessage
{
IsBodyHtml = true
};
message.Subject = "Confirm Email";
message.To.Add(item.email1);
message.Body = "Please confirm your account by clicking here";
client.SendAsync(message, "userToken");
//Assign Role User Here
await UserManager.AddToRoleAsync(user.Id, "Client");
}
}
}
}
SendEmailConfirmation method (outside Controller)
public async Task<string> SendEmailConfirmationTokenAsync(string userID)
{
var store = new UserStore<ApplicationUser>(db);
var UserManager = new ApplicationUserManager(store);
var url = new UrlHelper();
var provider = new DpapiDataProtectionProvider("MyApp");
UserManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
provider.Create("EmailConfirmation"));
string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
string encodedCode = HttpUtility.UrlEncode(code);
string callbackUrl = "http://localhost/Accounts/ConfirmEmail?userId=" + userID + "&code=" + encodedCode;
return callbackUrl;
}
where db is
ApplicationdDbContext db = new ApplicationdDbContext();
ConfirmEmail within the Identity Controller (Accounts Controller) - I've created Accounts instead of Account controller but it's working fine.
//
// GET: /Account/ConfirmEmail
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
var confirmed = await UserManager.IsEmailConfirmedAsync(userId);
if (confirmed)
{
return RedirectToLocal(userId);
}
var result = await UserManager.ConfirmEmailAsync(userId, code); //Here I get the error (Token Invlaid, despite the token and userId being displayed)
if (result.Succeeded)
{
ViewBag.userId = userId;
ViewBag.code = code;
}
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(SetPasswordViewModel model, string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
if (!ModelState.IsValid)
{
return View(model);
}
var result = await UserManager.AddPasswordAsync(userId, model.NewPassword);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(userId);
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
return RedirectToLocal(userId);
}
ViewBag.userId = userId;
ViewBag.code = code;
AddErrors(result);
return View(model);
}
I have worked for hours in this code but until now I can't sort it out.
Thanks for any comments or solution. The reason for this approach is that I have to use task scheduler(I'm using fluentscheduler, which is working fine).

UserManager.FindByNameAsync slow in ASP.NET MVC

I have a site based on ASP.NET MVC framework. Although i am using async method to login the user, i have found that it takes forever to get user logged in into the site. I have used Visual Studio's diagnostic tools and found that this line takes most of the time during the code execution.
var user = await UserManager.FindByNameAsync(model.Email);
Full Code:
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user != null)
{
var getPasswordResult = UserManager.CheckPassword(user, model.Password);
if (getPasswordResult)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
var identity = await UserManager.CreateIdentityAsync(
user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = model.RememberMe }, identity);
if (model.RememberMe == true)
{
HttpCookie cookie = new HttpCookie("UsersLogin");
cookie.Values.Add("UserName", model.Email);
cookie.Expires = DateTime.Now.AddDays(15);
Response.Cookies.Add(cookie);
}
return RedirectToAction("NavigateAuthUser", "Home", new { ReturnUrl = returnUrl });
}
else
{
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
return new EmptyResult();
}
Any suggestion to improve the performance?
Thanks
Sanjeev
For workaround I use:
_db.Users.First(x => x.UserName == User.Identity.Name);
But I'm sure this is not the best way
Develop my own method FindByEmail, this way I greatly improve the perfomance
public AspNetUsers FindByEmail(string Email)
{
try
{
var _modelo = new SIGMAEntities();
return _modelo.AspNetUsers.FirstOrDefault(x => x.Email.Trim().ToLower() == Email);
}
catch (Exception)
{
throw;
}
}

Explicit password and email validation in Microsoft.AspNet.Identity, why needed?

I am big fan of Adam Freeman's books. At his Pro Asp.net mvc 5 platform, in chapter 13, page 325, the following code confused me. Does anyone have the explanation why he used the email and password validation explicitly?
The call this.UserManager.UpdateAsync(user) should return a result with same errors generated by this.UserManager.UserValidator.ValidateAsync(user) and this.UserManager.PasswordValidator.ValidateAsync(password). Is he not doing the same thing twice? Or there is a special purpose?
[HttpPost]
public async Task<ActionResult> Edit(string id, string email, string password)
{
AppUser user = await this.UserManager.FindByIdAsync(id);
if (user != null)
{
user.Email = email;
IdentityResult validEmail = await this.UserManager.UserValidator.ValidateAsync(user);
if (!validEmail.Succeeded)
{
this.AddErrorsFromResult(validEmail);
}
IdentityResult validPass = null;
if (password != string.Empty)
{
validPass = await this.UserManager.PasswordValidator.ValidateAsync(password);
if (validPass.Succeeded)
{
user.PasswordHash = this.UserManager.PasswordHasher.HashPassword(password);
}
else
{
this.AddErrorsFromResult(validPass);
}
}
if ((validEmail.Succeeded && validPass == null)
|| (validEmail.Succeeded && password != string.Empty && validPass.Succeeded))
{
IdentityResult result = await this.UserManager.UpdateAsync(user);
if (result.Succeeded)
{
return this.RedirectToAction("Index");
}
this.AddErrorsFromResult(result);
}
}
else
{
ModelState.AddModelError(string.Empty, "User not found");
}
return this.View(user);
}
private AppUserManager UserManager
{
get
{
return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();
}
}
private void AddErrorsFromResult(IdentityResult result)
{
foreach (string error in result.Errors)
{
ModelState.AddModelError(string.Empty, error);
}
}
in source code of identity UserManager class UpdateAsync method is like this:
public virtual async Task<IdentityResult> UpdateAsync(TUser user)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException("user");
}
var result = await UserValidator.ValidateAsync(user).ConfigureAwait(false);
if (!result.Succeeded)
{
return result;
}
await Store.UpdateAsync(user).ConfigureAwait(false);
return IdentityResult.Success;
}
that calls UserValidator.ValidateAsync(user) method for validating that username is not illegal or user not registered before with a different Owner Id and does not care for validating Email address or password string. if you want to validate passwords and do your custom checks you must create custom validators .
you can find Default UserValidator source code here

Authorize Attribute with Roles

I want to implement my custom authorization, I wonder what is wrong with my code even I got the user credentials correctly it still redirects me to my Login Method, please see the code below
Edit: I have successfully implemented the Authorize Attribute with Roles, for future readers please see code below
Login Controller
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login (AdminViewModels.Login viewModel, string returnURL)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
PasswordHasher passwordVerify = new PasswordHasher();
var query = (from acc in db.accounts.Where(x => x.username == viewModel.Username)
select new { acc.username, acc.password}).FirstOrDefault();
if (query != null)
{
if (ModelState.IsValid)
{
var result = passwordVerify.VerifyHashedPassword(query.password, viewModel.Password);
switch (result)
{
case PasswordVerificationResult.Success:
//set forms ticket to be use in global.asax
SetupFormsAuthTicket(viewModel.Username, viewModel.rememeberMe);
return RedirectToLocal(returnURL);
case PasswordVerificationResult.Failed:
ModelState.AddModelError("", "Wrong Username or Password");
return View(viewModel);
}
}
}
return View(viewModel);
}
Forms Auth Ticket
private account SetupFormsAuthTicket(string userName, bool persistanceFlag)
{
account user = new account();
var userId = user.id;
var userData = userId.ToString(CultureInfo.InvariantCulture);
var authTicket = new FormsAuthenticationTicket(1, //version
userName, // user name
DateTime.Now, //creation
DateTime.Now.AddMinutes(20), //Expiration
persistanceFlag, //Persistent
userData);
var encTicket = FormsAuthentication.Encrypt(authTicket);
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
return user;
}
Global.asax
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//take out user name from cookies
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string[] roles = null;
trainingEntities db = new trainingEntities();
//query database to get user roles
var query = (from acc in db.account_roles where acc.account.username == username select acc.role.role_name).ToArray();
roles = query;
//Let us set the Pricipal with our user specific details
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles);
}
catch (Exception)
{
//somehting went wrong
}
}
}
}
Now you can use [Authorize(Roles = "Admin")]
to any action method or on top of controller
I have successfully implemented the Authorize Attribute with Roles, for future readers please see code below.
Login Controller
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login (AdminViewModels.Login viewModel, string returnURL)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
PasswordHasher passwordVerify = new PasswordHasher();
var query = (from acc in db.accounts.Where(x => x.username == viewModel.Username)
select new { acc.username, acc.password}).FirstOrDefault();
if (query != null)
{
if (ModelState.IsValid)
{
var result = passwordVerify.VerifyHashedPassword(query.password, viewModel.Password);
switch (result)
{
case PasswordVerificationResult.Success:
//set forms ticket to be use in global.asax
SetupFormsAuthTicket(viewModel.Username, viewModel.rememeberMe);
return RedirectToLocal(returnURL);
case PasswordVerificationResult.Failed:
ModelState.AddModelError("", "Wrong Username or Password");
return View(viewModel);
}
}
}
return View(viewModel);
}
FormsAuthTicket
private account SetupFormsAuthTicket(string userName, bool persistanceFlag)
{
account user = new account();
var userId = user.id;
var userData = userId.ToString(CultureInfo.InvariantCulture);
var authTicket = new FormsAuthenticationTicket(1, //version
userName, // user name
DateTime.Now, //creation
DateTime.Now.AddMinutes(20), //Expiration
persistanceFlag, //Persistent
userData);
var encTicket = FormsAuthentication.Encrypt(authTicket);
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
return user;
}
Global.asax
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//take out user name from cookies
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string[] roles = null;
trainingEntities db = new trainingEntities();
//query database to get user roles
var query = (from acc in db.account_roles where acc.account.username == username select acc.role.role_name).ToArray();
roles = query;
//Let us set the Pricipal with our user specific details
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles);
}
catch (Exception)
{
//somehting went wrong
}
}
}
}
Now you can use [Authorize(Roles = "Admin")]
to any action method or on top of controller
as I see in ControllerLogin attribute it is now being applied in a variable, when it should be applied to a method or a class
[CustomAuthorization(UserRole="Admin")]
// GET: Manage
private trainingEntities db = new trainingEntities();
public ActionResult Index()
{
return View();
}
Private trainingEntities dB = new TrainingEntities();
[CustomAuthorization(UserRole="Admin")]
Public ActionResult Index()
{
//yourcode
}

Resources