Where to put login logging method to capture userid in asp.net mvc? - asp.net-mvc

I'm attempting to create a record every time someone logs in, but I'm having an issue with capturing the userid during the login post. Here is the standard login POST. I've added in my LoginSysAction below the signin success. However my userid is null.
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
await LoginSysAction();
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);
}
Here is my logging method:
private async Task LoginSysAction()
{
var addSysAction = new SysAction();
addSysAction.Date = DateTime.UtcNow;
addSysAction.UserId = User.Identity.GetUserId();
addSysAction.ActionType = "Login";
db.SysActions.Add(addSysAction);
db.SaveChanges();
}
So my question is basically - is GetUserId() unavailable to me at this point? And if not, what would be the best method to achieve something similar?

GetUserId() is not available during this controller action. Nor is GetUserName, or probably any of the other Identity methods (though I haven't tried them). I was also surprised that they weren't available!
You can, however, lookup the user id using UserManager.FindByEmailAsync() which returns an ApplicationUser, which inherits from the IdentityUser. Since you know the email that was just used to successfully log in, just look up the user.
case SignInStatus.Success:
var justLoggedIn = await UserManager.FindByEmailAsync(model.Email);
await LoginSysAction(justLoggedIn.Id);
return RedirectToLocal(returnUrl);
Obviously, you'd need change your LoginSysAction a bit to accommodate this.
It's not exactly efficient, though, doing repeated db calls, so it might be worth incorporating the userid lookup into your actual database query that stores the login information.
A nicer place to do the logging might be in SignInManager.PasswordSignInAsync which you can override.
public override async Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout)
{
if (UserManager == null)
return SignInStatus.Failure;
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
return SignInStatus.Failure;
if (await UserManager.IsLockedOutAsync(user.Id))
return SignInStatus.LockedOut;
if (await UserManager.CheckPasswordAsync(user, password))
{
await UserManager.ResetAccessFailedCountAsync(user.Id);
await SignInAsync(user, isPersistent, false);
// put your logging here!
await LoginSysAction(user.Id);
return SignInStatus.Success;
}
if (shouldLockout)
{
await UserManager.AccessFailedAsync(user.Id);
if (await UserManager.IsLockedOutAsync(user.Id))
return SignInStatus.LockedOut;
}
return SignInStatus.Failure;
}

Related

ASP.NET MVC "SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);" return failure all the time

I've made some changes in my ExternalLogin actions. I added a new field to my user table, for the first name, and I added code as to request the first name from facebook. It could be the reason why I am getting the error. I will not post the app secret for facebook external login here, but you know it is present in my code. When I register a new user with facebook it works and keeps the user logged in, and adds the user to the database, also gets the correct first name. However When I log off and attempt to log in with facebook, it forces me to register instead of logging me in (it redirects to the register with facebook page, saying "you have successfully authenticated with facebook. Please enter a user name for this site below and click the Register button to finish logging in.")
Here is the code with the changes I made:
Inside the Startup.Auth class:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions()
{
AppId = "2737863089761791",
AppSecret = "",
BackchannelHttpHandler = new HttpClientHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.8/me?fields=id,name,email,first_name,last_name",
Scope = { "email" },
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = async context =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
foreach (var claim in context.User)
{
var claimType = string.Format("urn:facebook:{0}", claim.Key);
string claimValue = claim.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, "XmlSchemaString", "Facebook"));
}
}
}
});
Inside my ExternalLoginCallback action in AccountController:
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
var firstName = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:first_name").Value;
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: 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 = false });
case SignInStatus.Failure:
default:
// 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, Name = firstName });
}
I figured it redirects me to the register page, because the line
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
returns failure inside ExternalLoginCallback and I don't know why. It must return success to get to the case:
case SignInStatus.Success
I had an error in my ExternalLoginConfirmation action, so i put some code in a try-catch block. It solved my problem, but I still don't know where the error was coming from:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "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.Name, Email = model.Email , Name = model.Name};
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
try
{
var useraux = await UserManager.FindByEmailAsync(model.Email);
result = await UserManager.AddLoginAsync(useraux.Id, info.Login);
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
Response.Write("Object: " + eve.Entry.Entity.ToString());
Response.Write(" " +
"");
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
Response.Write(ve.ErrorMessage + "" +
"");
}
}
//throw;
}
if (result.Succeeded)
{
//--
//await StoreFacebookAuthToken(user);
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}
AddErrors(result);
}
ViewBag.ReturnUrl = returnUrl;
return View(model);
}

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;
}
}

C# MVC ASP.NET Identity - Dynamically change ApplicationDbContext connection string during runtime

I have 3 databases, one GlobalDb is used to manage and redirect users the correct database based on login details.
The other 2 databases are different to the GlobalDb but are the the same as each other.
Lets call these 2 databases Company1 and Company2.
GlobalDb does not require the identity framework so it is just being connected to by using standard EntityFramework connections, the other 2 do required the Identity framework.
3 connection strings are saved in the web.config.
I can query all databases bases correctly, and they all return data.
The main issue i am having is on login, how do i tell the SignInManager which connection string for the database to use?
IdentityModels.cs
public class CompanyDb : IdentityDbContext<ApplicationUser>
{
public CompanyDb(string CompanyName)
: base(CompanyName, throwIfV1Schema: false)
{
}
public static CompanyDb Create(string CompanyName)
{
return new CompanyDb(CompanyName);
}
// Virtual class and table mappings go here.
}
AccountController Login
public async Task<ActionResult> CustomLogin(string Email, string Password, string returnUrl)
{
GlobalDb Global = new GlobalDb();
// check what company the user belongs to based on email.
var CompanyName = Global.Users.Where(u => u.Email == Email && u.Active == true).Select(u => u.Company).FirstOrDefault();
// Connect to the desired database to get test data.
CompanyDb Company = new CompanyDb(CompanyName);
var DataTest = Company.Users.ToList();
if (CompanyName != null)
{
var result = await SignInManager.PasswordSignInAsync(Email, Password, false, shouldLockout: false); // <-- How to connect to the correct DB?
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View("");
}
}
return View("");
}
I keep getting a null value in the following file and function
IdentityModel.cs
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
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;
}
}
Ive Figured it out.
On my login action if just wrapped the SignInManager within a Context Using statement as follows.
public async Task<ActionResult> CustomLogin(string Email, string Password, string returnUrl)
{
GlobalDb Global = new GlobalDb();
// check what company the user belongs to based on email.
var CompanyName = Global.Users.Where(u => u.Email == Email && u.Active == true).Select(u => u.Company).FirstOrDefault();
if (CompanyName != null)
{
using (CompanyDb db = new CompanyDb(CompanyName))
{
var result = await SignInManager.PasswordSignInAsync(Email, Password, false, shouldLockout: false); // <-- How to connect to the correct DB?
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View("");
}
}
}
return View("");
}

mvc redirect does not work after I login

I have a mvc controller called Auth, and a action called login, this look like this..
[HttpPost]
public async Task<ActionResult> LogIn(LogInModel model)
{
if (!ModelState.IsValid)
{
return View();
}
var user = await userManager.FindAsync(model.Email, model.Password);
if (user != null)
{
await SignIn(user);
if (!string.IsNullOrWhiteSpace(model.ReturnUrl))
{
return Redirect(GetRedirectUrl(model.ReturnUrl));
}
else
{
//return RedirectToAction("Index", "Home");
return Redirect("../Home/Index");
}
}
else
{
return View();
}
// user authN failed
//ModelState.AddModelError("", "Invalid email or password");
//return View();
}
But I'm not redirected to my ../Home/Index. I stay on the same page even if I succed to login. What could be wrong here?
What version of MVC are you using? I have secured pages with a login in the past using the below method and checking an enum that is part of asp.net Identitiy, as you can see the return URL is passed in to the method.
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// 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.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);
}
}
If you are using MVC 4 then you can use the [AllowAnonymous]
Had the same problem, after method invocation changed to Ajax. Simply changed the response type to JavaScript and asked it execute a navigation since the results of the call is being handled inside a Javscript code block
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
var returnUrlJs = string.Format("location.href='{0}'", returnUrl);
return new JavaScriptResult() { Script = returnUrlJs };

ASP.net MVC Authentication Cookie is dismissed after closing browser [duplicate]

I am using asp.net identity 2.0 to manage user logins.
I am following the sample for Identity 2.0 and cannot get the cookie to persist after the whole browser is closed. This is happening on all browsers.
Code:
Account Controller
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await SignInHelper.PasswordSignIn(model.Email, model.Password, isPersistent: true, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
SignInHelper
public async Task<SignInStatus> PasswordSignIn(string userName, string password, bool isPersistent, bool shouldLockout)
{
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
{
return SignInStatus.Failure;
}
if (await UserManager.IsLockedOutAsync(user.ID))
{
return SignInStatus.LockedOut;
}
if (await UserManager.CheckPasswordAsync(user, password))
{
// password verified, proceed to login
return await SignIn(user, isPersistent);
}
if (shouldLockout)
{
await UserManager.AccessFailedAsync(user.ID);
if (await UserManager.IsLockedOutAsync(user.ID))
{
return SignInStatus.LockedOut;
}
}
return SignInStatus.Failure;
}
-
private async Task<SignInStatus> SignIn(User user, bool isPersistent)
{
await SignInAsync(user, isPersistent);
return SignInStatus.Success;
}
-
public async Task SignInAsync(User user, bool isPersistent)
{
var userIdentity = await user.GenerateUserIdentityAsync(UserManager);
AuthenticationManager.SignIn(
new AuthenticationProperties
{
IsPersistent = isPersistent
},
userIdentity
);
}
Startup.Auth
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieName = "ApplicationCookie",
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = System.TimeSpan.FromMinutes(180), // 3 hours
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = ApplicationCookieIdentityValidator.OnValidateIdentity(
validateInterval: TimeSpan.FromMinutes(0),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (user) => (user.GetGuidUserId()))
}
});
Sorry for the wall of code, but I can't see what I am doing wrong, that the cookie wouldn't be persisted for the 3 hours, when the browser was closed without manually logging off?
The issue is with a bug in the OnValidateIdentity which when regenerating the cookie currently always sets IsPersistent to false (even if the original cookie was persistent). So because you set validateInterval to 0 (always validate every request), you effectively never will get a persistent cookie.

Resources