External login callback signs incorrect user - asp.net

I have a strange problem with ExternalLoginCallback method. I am logging some information in the log and what is interesting Email is correct, but then userId is incorrect and it belongs to another user who was logged previously.
I.e. some UserA is logged into the system and now UserB wants to log into the system in another window. I am expecting that in new window UserB will be logged in and overwrite cookies, so if I refresh first window it will show UserB, but somehow that is not happening and in second window it shows UserA.
Here is the code:
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
EventLogManager.LogWarning(loginInfo.Email);
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: true);
switch (result)
{
case SignInStatus.Success:
{
var userId = SignInManager.AuthenticationManager.AuthenticationResponseGrant.Identity.GetUserId();
EventLogManager.LogWarning(userId);
...
EDIT
I think I should add more clarifications. There is some action which is being called from third party - Shopify. It looks like:
public ActionResult Callback(string code, string hmac, string shop, string state, string timestamp)
{
//this resolved the issue
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
Session["Workaround"] = 0;
return new ChallengeResult("Shopify", Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = "/Product/Products" }), null, shop.Replace(".myshopify.com", ""));
}

Related

Details of ResetPasswordAsync method of UserManager

I want to know how the ResetPasswordAsync() method of .net works from the inside, I tried a lot but unable to find any articles about it, please help.
Below is the code for ResetPasswordAsync()
public virtual async Task<IdentityResult> ResetPasswordAsync(TUser user, string token, string newPassword)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
// Make sure the token is valid and the stamp matches
if (!await VerifyUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider, ResetPasswordTokenPurpose, token))
{
return IdentityResult.Failed(ErrorDescriber.InvalidToken());
}
var result = await UpdatePasswordHash(user, newPassword, validatePassword: true);
if (!result.Succeeded)
{
return result;
}
return await UpdateUserAsync(user);
}
If user has clicked on forgot password then you need to check if user exists then generate a token key for password reset like below
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: link");
After that either send recovery mail like shown above or send to your own view for recovery.
Code is taken from Microsoft GitHub Account

asp.net mvc multitenant database per tenant

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

Asp.NET MVC 5 validate user by e-mail and password

I'm using a Asp.NET MVC 5 project that came with a Bootstrap 3 theme we bought and in its login method they just look for the user based on his e-mail, the password is not validated. Login method below:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(AccountLoginModel viewModel)
{
// Ensure we have a valid viewModel to work with
if (!ModelState.IsValid)
return View(viewModel);
// Verify if a user exists with the provided identity information
var user = await _manager.FindByEmailAsync(viewModel.Email);
var hashPass = new PasswordHasher().HashPassword(viewModel.Password); // this is a line I added which gerenates a different hash everytime
// If a user was found
if (user != null)
{
// Then create an identity for it and sign it in
await SignInAsync(user, viewModel.RememberMe);
// If the user came from a specific page, redirect back to it
return RedirectToLocal(viewModel.ReturnUrl);
}
// No existing user was found that matched the given criteria
ModelState.AddModelError("", "Invalid username or password.");
// If we got this far, something failed, redisplay form
return View(viewModel);
}
The line I'm trying to insert the password validation is the if (user != null). I tried using _manager.Find(email,password) but it doesn't work.
How can I login the user with his e-mail and validate the password?
That is because you are hashing the password before trying to find the user.
Do
var user = _manager.Find(viewModel.Email, viewModel.Password);
// If a user was found
if (user != null)
{
//...other code removed for brevity.
which is the standard way to do it.
-------Try this code------
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return View("SuccessView");
case SignInStatus.Failure:
return View("LoginView");
}

Invalid token in ConfirmEmail due to changed Securitystamp

I've been banging my head against a wall for some time now about this:
I have an ASP.NET MVC 5.2.3 web application with ASP.NET Identity 2.2.1. I want to force users to
validate their email-address and
validate their mobile phone number.
So when a user registers for the application an emailVerification token is generated and sent to the user.
After that the user is redirected to the VerifyPhoneNumber endpoint in the Manage controller. SMS-code is generated and gets send to the user. User is promted to enter the SMS-code. Code is verified.
BUT if then the user receives the email with the email-verification-code and click the link the token cannot no longer be verified (Invalid Token).
As far as I understand, this happens because calling UserManager.ChangePhoneNumberAsync changes the user's SecurityStamp. Email-verification works well if phone verification is not active. To be more specific, when ChangePhoneNumberAsync is not called.
Any ideas on how to prevent the SecurityStamp from changing or allow both verifications on inital registration are greatly appreciated.
Ben
VerifyPhoneNumber
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var userId = User.Identity.GetUserId();
var result = await UserManager.ChangePhoneNumberAsync(userId, model.PhoneNumber, model.Code);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(userId);
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToAction("Index", "Home");
}
else
{
return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "Could not verify phone number.");
return View(model);
}
ConfirmEmail
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
code = HttpUtility.UrlDecode(code);
var result = await UserManager.ConfirmEmailAsync(userId, code);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}

Roles Create New DataBase When I Add a user to a role in MVC 5

i create my own database and add user identity table to this by change the connection string.
now my connection string is this:
when i create a new user it worked well.
but when i change the Register(RegisterViewModel model) in RegisterControler to add a user to a role like this code:
public async Task Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
//add user to member role******************
if (!Roles.RoleExists("Member"))
Roles.CreateRole("Member");
Roles.AddUserToRole(model.Email, "Member");
//*******************************************
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);
}
user registerd but dont add to member Role! and it seem there is another connection string for Roles! beacause whene run this code ASPNETDB.MDF created in App_Data!
Please help me to solve this problem
In order to create roles in asp.net identity, you need to use AspNetRoleManager same as you are currently using AspNetUserManager.
The AspNetUserManager may looks like below.
public class AspNetRoleManager : RoleManager<IdentityRole, string>
{
public AspNetRoleManager(IRoleStore<IdentityRole, string> roleStore)
: base(roleStore)
{
}
public static AspNetRoleManager Create(IdentityFactoryOptions<AspNetRoleManager> options, IOwinContext context)
{
return new AspNetRoleManager(new RoleStore<IdentityRole, string, IdentityUserRole>(context.Get<YourDataContext>()));
}
}
Then you need to register AspNetRoleManager in the owin startup. Same like the AspNetUserManager.
app.CreatePerOwinContext<AspNetRoleManager>(AspNetRoleManager.Create);
After that you can use it inside the controller to create roles.
var roleManager = HttpContext.GetOwinContext().Get();
// Check for existing roles
var roleManager = HttpContext.GetOwinContext().Get<AspNetRoleManager>();
var roleExists = await roleManager.RoleExistsAsync("Member");
if (!roleExists)
{
var role = new IdentityRole();
role.Name = "Member";
var result = roleManager.CreateAsync(role);
}
Then add new role to the user.
var user = await UserManager.FindByEmailAsync(model.Email);
var roleRsult = UserManager.AddToRole(user.Id, roleName);

Resources