Why user is null - asp.net-mvc

I would like to ask, why UserId is null (in code below) after user log in.
OR
I need redirect users depending on the they roles. I need make it As Simple As Possible.
// 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.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
string UserId = User.Identity.GetUserId(); <------ HERE
HelpRoles hr = new HelpRoles();
returnUrl = hr.CheckUserRoleAndRedirect(UserId);
return RedirectToLocal(returnUrl);

The User object is set based on the decrypted cookies after the request is authenticated, and these cookies aren't set until the next request so the page needs to be refreshed before User.Identity is not null.
For more info, see this post.
To work around this, within the success case you can get the user from the DB using the email address and use the ID on that instead.

Like John Mc said to work around this you have to the get user in the Success case. So you might want something like this before getting UserId
var user = userManager.Find(model.UserName, model.Password);
or
var user = userManager.FindByEmail(model.Email);
or
var user = userManager.FindByName(model.Username);
EDIT
In this case you wouldn't have to use your method as shown below
string UserId = User.Identity.GetUserId(); <------ HERE
because once you get the user object you can get the id field in it like user.Id

Related

Invalidate ClaimsPrincipal after it has been modified

I am using ASP.NET MVC, Identity2.
I have added "FirstName" Custom ClaimPrincipal:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, long> manager)
{
var userIdentity = await manager.CreateIdentityAsync(
this,
DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim("FirstName", FirstName));
return userIdentity;
}
If I update the value of "FirstName", I need to logout and log back in, for the "FirstName" Claim to be updated. Is it possible to invalidate "FirstName" Claim, so it's value is forced to be refreshed?
I have seen this question, which shows how to update the value of Claims, I was wondering if there is easier way to just invalidate them.
When looking at MS built-in template, I noticed that they alway makes a call to SignInManager.SignInAsync, after changing user credentials (e.g. password, 2 Factor Authentication, etc).
I also noticed that the Claims are updated once the user logs out and logs back in... so after changing "FirstName" which is stored in a Claim, I called SignInManager.SignInAsync to re-signin the User... this way, the Claims are updated:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UpdateFirstName(string firstName)
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId<long>());
user.FirstName = firstName;
// update FirstName which is stored in a Claim
var result = await UserManager.UpdateAsync(user);
if (result.Succeeded)
{
// re-signin the user, to refresh the Claims
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// you need to redirect after calling SignInAsync, so claims are refreshed
return RedirectToAction("Index");
}
// add some error message...
return View();
}
Note: As shown in the question, I am storing the Claims in Cookie.

Invalid login attempt in MVC

I am currently changing the default template of MVC5 project (with identity). When I first time register and login, all goes well. But when I made some changes like project at default level stores UserName as Email. But when I make change it to store UserName as from Form then it gives 'Invalid' login attempt. Can anyone tell me that where I am going wrong. Either I have to make changes at any other places if yes then where?
Here are my register function:
Before:
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
I have changed this line to this:
var user = new ApplicationUser { UserName = model.usrName, Email = model.Email , PhoneNumber=model.Contact};
And my login function is as it comes in default. like,
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.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);
}
}
Now, can someone please tell me where i am wrong?
If you want to change UserName from model.Email to model.usrName, you have to use model.usrName in your Login function. Like this:
var result = await SignInManager.PasswordSignInAsync(model.usrName, model.Password, model.RememberMe, shouldLockout: false);
Because UserName and Password fields are default unique parameters to login. So it wants to complete login operation with UserName field.

How do I check to see if a user exists already in the controller?

Here is my register method. I changed the register method into a Admin tool. The admin tool works properly without the if statement. However, it does not like the way I am looking for existing users.
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
if(_context.Users.Contains(model.UserName))//Errors out here
{
var user = new ApplicationUser { UserName = model.UserName};
user.UserName = model.UserName;
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await this.UserManager.AddToRoleAsync(user.Id, model.Name);
return RedirectToAction("Index","User");
}
AddErrors(result);
}
}
return View(model);
}
Try instead:
if(!_context.Users.Any(x => x.UserName == model.UserName))
Assuming the username in Users is called UserName.. Any returns true or false if the condition exists.
I think your logic is backwards, anyways. I think you want to create a user if they do NOT exist, which is what I did above.
It's probably better, however, to use the UserManager for this.
var user = await UserManager.FindByNameAsync(model.UserName);
if (user != null)
...
The reason why it's best to use the UserManager for everything is because you aren't creating multiple conflicting DbContexts (UserManager has one, and you create a separate one). This can lead to issues where you have objects created with one context that you try to use in the other, and it results in all kinds of headaches.
The most efficient way, however, is to simply try and create the user, and check the result for success or failure.
var result = await UserManager.CreateAsync(...)
if (result.Succeeded)
....
You're doing that above already, so what is the point of doing the extra check first?

ASP.NET MVC Login by email instead of username

I try to create new project with MVC 5 and OWIN by "Individial User Accounts". The problem is when I register a new account, the system requires me input an email then that email will be populated to both Email and Username in database.
When I try to login, the system asks me to input the email but it compares that email with column Username. So the values of Email and Username are the same. The login code below is in action Login (Account Controller)
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
Is there any ways to compare the inputted email with the Email column instead of Username?
To answer:
Is there any ways to compare the inputted email with the Email column instead of Username?
This is probably not the best answer but the SignInManager should have access to UserManager which has a Users property.
So you could lookup the user based on the email and then using the result call the signin method.
Something like this (untested):
var user = SignInManager.UserManage.Users.Where(u => u.email == model.Email).FirstOrDefault();
var result = await SignInManager.PasswordSignInAsync(user.UserName, model.Password, model.RememberMe, shouldLockout: false);
You would want to check that the user was populated otherwise return invalid login details error. Same as if PasswordSignInAsync failed.
Here is what to do as #ChrisMoutray suggested, find the username for the email provided and log in. I put it here.
Instantiate usermanager and signinmanager
private readonly SignInManager<ApplicationUser> signInManager;
private readonly UserManager<ApplicationUser> userManager;
public AccountController(SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager)
{
this.signInManager = signInManager;
this.userManager = userManager;
}
and then in the Login (post) task:
var user = await userManager.FindByEmailAsync(model.Email);
var result = await signInManager.PasswordSignInAsync(user.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
PS. Found myself in this old post so just adding a working solution.
The reason is because when you register. you get username = email.
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);

prevent users without confirmed email from logging in ASP.Net MVC with Identity 2

In microsoft Identity 2 there is ability to users can confirm there email addresses I downloaded Identity 2 sample project from here in this project there isn't any difference between users confirmed their emails and who doesn't I want to people how don't confirmed their emails can't login this is what I tried :
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
{
var user = await UserManager.FindByNameAsync(model.Email);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
//first I tried this.
//return LogOff();
HttpContext.Server.TransferRequest("~/Account/LogOff");
return RedirectToAction("Login");
}
}
return RedirectToLocal(returnUrl);
}
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
I tried to force user to Logoff by calling LogOff() action method but It didn't work and user remain authenticated .then I tried to use Server.TransferRequest() but I don't know why it did the job but it redirects users to login page with returnUrl="Account/Logoff"
so after they confirmed their email and tried to login they get logoff I get really confused!!
this is my LogOff() action method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
return RedirectToAction("About", "Home");
}
I have googled it for days without any luck !!!!
Maybe its a little late but I hope it may help others.
Add this
var userid = UserManager.FindByEmail(model.Email).Id;
if (!UserManager.IsEmailConfirmed(userid))
{
return View("EmailNotConfirmed");
}
before
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
The first block of code just checks if the email in the model exists in the database and gets it's id to check if it is not confirmed and if so returns a view to the user wich says so and if it is confirmed just lets the user sign in.
And delete your changes to the result switch like this
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
Instead of moving to another page, why not finish this one and redirect to the right action / view:
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
return RedirectToAction("ConfirmEmailAddress", new { ReturnUrl = returnUrl });
}
You do need an action (and possibly a view) with the name ConfirmEmailAddress though.
There is a solution, which may not be the best approach, but it works. First let me try to clarify why your approach did not work.
In one of the comments it is mentioned, the AuthenticationManager uses cookies. In order to update a cookie you need to send it to the client, using another page. That is why TransferRequest is not going to work.
How to handle the emailverification? The strategy I used:
1) On SignInStatus.Success this means that the user is logged in.
2) When email is not confirmed: send an email to the used e-mailaddress. This is safe since the user already signed in. We are just blocking further access until the e-mail is verified. For each time a user tries to login without having validated the email, a new email (with the same link) is sent. This could be limited by keeping track of the number of sent emails.
3) We cannot use LogOff: this is HttpPost and uses a ValidateAntiForgeryToken.
4) Redirect to a page (HttpGet, authorization required) that displays the message that an e-mail has been sent. On entering sign out the user.
5) For other validation errors, redirect to another method to sign out (HttpGet, authorization required). No view needed, redirect to the login page.
In code: update the code in AccountController.Login to:
case SignInStatus.Success:
{
var currentUser = UserManager.FindByNameAsync(model.Email);
if (!await UserManager.IsEmailConfirmedAsync(currentUser.Id))
{
// Send email
var code = await UserManager.GenerateEmailConfirmationTokenAsync(currentUser.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = currentUser.Id, code = code}, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(currentUser.Id, "Confirm your account", string.Format("Please confirm your account by clicking this link: link", callbackUrl));
// Show message
return RedirectToAction("DisplayEmail");
}
// Some validation
if (true)
{
return RedirectToAction("SilentLogOff");
}
return RedirectToLocal(returnUrl);
}
Add methods to AccountController:
// GET: /Account/SilentLogOff
[HttpGet]
[Authorize]
public ActionResult SilentLogOff()
{
// Sign out and redirect to Login
AuthenticationManager.SignOut();
return RedirectToAction("Login");
}
// GET: /Account/DisplayEmail
[HttpGet]
[Authorize]
public ActionResult DisplayEmail()
{
// Sign out and show DisplayEmail view
AuthenticationManager.SignOut();
return View();
}
DisplayEmail.cshtml
#{
ViewBag.Title = "Verify e-mail";
}
<h2>#ViewBag.Title.</h2>
<p class="text-info">
Please check your email and confirm your email address.
</p>
You'll notice that the user cannot reach other pages until email is verified. And we are able to use the features of the SignInManager.
There is one possible problem (that I can think of) with this approach, the user is logged in for the time that the email is sent and the user is being redirected to the DisplayMessage view. This may not be a real problem, but it shows that we are not preventing the user from logging in, only denying further access after logging in by automatically logging out the user.
=== Update ====
Please note that exceptions have to be handled properly. The user is granted access and then access is revoked in this scenario. But in case an exception occurs before signing out and this exception was not catched, the user remains logged in.
An exception can occur when the mailserver is not available or the credentials are empty or invalid.
===============
I would let the admin create the user without any password. The email with link should go to the user. The user then is directed to SetPassword page to set new password. This way no one can access the user account unless he confirms and sets the password.
Call CreateAsync without the password
var adminresult = await UserManager.CreateAsync(user);
Redirect admin to new custom view saying something like "Email is sent to user"
#{
ViewBag.Title = "New User created and Email is Sent";
}
<h2>#ViewBag.Title.</h2>
<p class="text-info">
The New User has to follow the instructions to complete the user creation process.
</p>
<p class="text-danger">
Please change this code to register an email service in IdentityConfig to send an email.
</p>
The answer by #INFINITY_18 may cause Object reference not set to an instance of an object error if the email does not exist in the data store at all. And why not return the Login view with model error in this case, too?
I would suggest the following:
var userid = UserManager.FindByEmail(model.Email)?.Id;
if (string.IsNullOrEmpty(userid) || !UserManager.IsEmailConfirmed(userid)))
{
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
Require email confirmation
It's a best practice to confirm the email of a new user registration to verify they are not impersonating someone else (that is, they haven't registered with someone else's email). Suppose you had a discussion forum, and you wanted to prevent "yli#example.com" from registering as "nolivetto#contoso.com." Without email confirmation, "nolivetto#contoso.com" could get unwanted email from your app. Suppose the user accidentally registered as "ylo#example.com" and hadn't noticed the misspelling of "yli," they wouldn't be able to use password recovery because the app doesn't have their correct email. Email confirmation provides only limited protection from bots and doesn't provide protection from determined spammers who have many working email aliases they can use to register.
You generally want to prevent new users from posting any data to your web site before they have a confirmed email.
Update ConfigureServices to require a confirmed email:
​
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
services.Configure<AuthMessageSenderOptions>(Configuration);
}

Resources