ASP MVC ConfirmEmailAsync not working - c#

I register a user, receive a token via email which looks like this:
Please confirm your account by clicking here
I click the link and I can see that the ConfirmEmail method in AccountController fires:
[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);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
And that result.Succeeded is true.
Everything appears fine, but when trying to log in after completing this process I get taken to the page telling me my account is locked
Locked out.
This account has been locked out, please try again later.
What couldI be doing wrong? Do I need to manually change the lockout flag in the db? If so, what is the point of the ConfirmEmailAsync method?

ConfirmEmailAsync just sets the EmailConfirmed on the user account record to true. From UserManager (edited for brevity):
public virtual async Task<IdentityResult> ConfirmEmailAsync(TUser user, string token)
{
...
var store = GetEmailStore();
...
await store.SetEmailConfirmedAsync(user, true, CancellationToken);
return await UpdateUserAsync(user);
}
Where GetEmailStore returns the IUserEmailStore (which is implemented by UserStore by default), which sets the flag:
public virtual Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken))
{
...
user.EmailConfirmed = confirmed;
return Task.CompletedTask;
}
The error you're getting indicated that the LockoutEnabled flag on the user account is true. You can set this to false by calling the SetLockoutEnabledAsync method on the UserManager.
There is also a SupportsUserLockout flag on the UserManager which unlocks accounts by default on creation. In order to set this you will need to create your own UserManager and override this flag to false.

At first, I had challenges getting these to work and after a series of research no success. Finally, I got the root of the problem(s) and fixed them thus sharing my experience. Follow the following process and I am sure it will help.
Step 1
Goto Startup.cs and remove the code below if you have it initialised;
services.Configure<RouteOptions>(options =>
{
options.LowercaseUrls = true;
//options.LowercaseQueryStrings = true; //(comment or remove this line)
});
Step 2 For GenerateEmailConfirmationTokenAsync() / ConfirmEmailAsync()
2a. On registering new user for token generation go as thus;
var originalCode = await userManager.GenerateEmailConfirmationTokenAsync(user);
var code = HttpUtility.UrlEncode(originalCode);
var confirmationLink = Url.Action("ConfirmEmail", "Account",
new { userId = user.Id, token = code }, Request.Scheme);
2b. On receiving confrimationLink for email confirmation, go as thus
var originalCode = HttpUtility.UrlDecode(token);
var result = await userManager.ConfirmEmailAsync(user, originalCode);
if (result.Succeeded)
{
return View(); //this returns login page if successful
}
For GeneratePasswordResetTokenAsync() and ResetPasswordAsync()
a.
var originalCode = await userManager.GeneratePasswordResetTokenAsync(user);
var code = HttpUtility.UrlEncode(originalCode);
var passwordResetLink = Url.Action("ResetPassword", "Account",
new { email = model.Email, token = code }, Request.Scheme);
b.
var orginalCode = HttpUtility.UrlDecode(model.Token);
var result = await userManager.ResetPasswordAsync(user, orginalCode, model.Password);
if (result.Succeeded)
{
return View("ResetPasswordConfirmation");
}

Related

Asp Net Core Identity get user id on login page

on my login page of in Areas/Identity/Account/Login
I have this handler, I want to log which user is accessing the database by getting his Id. The problem is the claims are still empty even after the signing result is successful. Am I missing something? I know i can just find the user Id in database using the username but that's just an extra database call i'd like to avoid if possible.
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/Account/Index");
if (!ModelState.IsValid)
{
_toast.AddErrorToastMessage("Dados inválidos.");
return Page();
}
// 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(Input.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: false);
var user = await _signInManager.UserManager.GetUserAsync(User);
if (result.Succeeded)
{
_context.Accesses.Add(new Access
{
Description = "Login",
Machine = Environment.MachineName,
UserId = User.GetUserId(),
IpAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString()
});
try
{
await _context.SaveChangesAsync();
}
catch(DbUpdateException ex)
{
_logger.LogInformation(ex.InnerException.ToString());
}
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
_toast.AddErrorToastMessage("Username / Password Incorrecto(s).");
return Page();
// If we got this far, something failed, redisplay form
}
Instead of
_signInManager.UserManager.GetUserAsync(User);
try using
_signInManager.UserManager.FindByNameAsync(Input.UserName)

Get User From Usermanger After Login

I'm trying to get a user from the usermanger after External Login, using .Net-Core and IdentityServer4
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToAction(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
var user = await _userManager.GetUserAsync(HttpContext.User);
var serviceProvider = HttpContext.RequestServices;
var context = serviceProvider.GetRequiredService<PublicAstootContext>();
var coinbaseRule = new CoinbaseRule();
await coinbaseRule.UpdateCoinbaseAccountInfo(context, user.Id, info);
//...
}
}
However, even after the login succeeds, when I attempt to get the user from the usermanger, the user is always null.
How can I get the user from the usermanger after my external login?
The call to ExternalLoginSignInAsync does not populate HttpContext.User - it ends up writing an authentication cookie that is read in subsequent requests when attempting to populate HttpContext.User, but not before. In your example, the call to ExternalLoginSignInAsync occurs within the same request as the call to GetUserAsync, which means that HttpContext.User will not represent an authenticated user and so no match will be found.
Instead, you can use UserManager.FindByLoginAsync to get the correct value for user:
var user = await _userManager.FindByLoginAsync(
info.LoginProvider, info.ProviderKey);

User.Identity.GetUserId() returns null in AccountsController but works fine in HomeController [duplicate]

I've defined a temp variable to get current user id, it always returns null.
Here is the snapshot:
Why?
UPDATE:
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return Json(new { success = false, ex = "Fail to login." });
}
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, isPersistent: true, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
string userId = User.Identity.GetUserId();
return Json(new { success = true });
case SignInStatus.Failure:
return Json(new { success = false, ex = "Email or password was incorrect." });
default:
return Json(new { success = false, ex = "Fail to login." });
}
}
UPDATE 2:
On client side, I use ajax to connect to /Account/Login:
var loginAjax = function (email, password, callback) {
$.ajax({
url: '/Account/Login',
type: 'POST',
data: { Email: email, Password: password },
success: function (data) {
$('body').css('cursor', 'default');
if (data.success) {
callback(true)
} else {
$('#login-error').text(data.ex)
}
},
error: function () {
$('#login-error').text('Không thể kết nối đến máy chủ.')
}
});
callback(false)
};
// I've got email and password in another function to check valid or not
loginAjax(email, password, function (success) {
$('body').css('cursor', 'default');
switch (success) {
case true:
signin(function () {
$('.login').html('');
window.location.href = '/?type=Promotion';
});
break
case false:
$('#Email-active').hide();
$('#Password-active').hide();
$('#Password').val('');
$('#login-btn').removeClass('disabled').attr('onclick', '$(this).addClass("disabled").removeAttr("onclick"); running()');
break
}
});
SignalR on client side:
var signalR = $.connection.chat;
var signin = function (callback) {
$.connection.hub.start().done(function () {
signalR.server.signinToSignalR();
callback()
})
};
SignalR on server side:
public void SigninToSignalR()
{
// this's always null
string userId = HttpContext.Current.User.Identity.GetUserId();
}
Simply try this :
string userId = SignInManager
.AuthenticationManager
.AuthenticationResponseGrant.Identity.GetUserId();
Actually, the user is not signed in - not in the context of the current request (the POST /Account/Login request), which is where User.Identity gets its data. If you want to extract the id of the user currently trying to (and apparently succeeding) to sign in, you need to do that in some other way, like hijacking some step inside the call to SignInManager.PasswordSignInAsync. If you are implementing your own MembershipProvider, this should be easy.
Otherwise, you will have to wait for the next request (any request handled by some Controller's Action method should do fine) to use User.Identity in the way you want to.
Some added explanation
When your Login method gets called, the request context is already evaluated and a lot of data is available. For example HTTP headers, cookies and so on. This is where all the context information is found, like User.Identity.
When you call SignInManager.PasswordSignInAsync(...), this does not affect the values of the request context, because this would make no sense – since the browser has not changed its mind about what it sent a few milliseconds ago. What it does affect is the response context to add a cookie containing some user and session id. This cookie is then sent to the browser, which then sends it back to server for each successive request. So all requests later than this one (until the user signs out or the cookie gets too old) will include information for the User.Identity to interpret.
You could, in your case use other data to find the user that just logged in. Since we know that the login is successful and username is unique the following will work;
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return Json(new { success = false, ex = "Fail to login." });
}
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, isPersistent: true, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
string userId = UserManager.FindByName(model.Email)?.Id;
return Json(new { success = true });
case SignInStatus.Failure:
return Json(new { success = false, ex = "Email or password was incorrect." });
default:
return Json(new { success = false, ex = "Fail to login." });
}
}
HttpContext.User = await _signInManager.CreateUserPrincipalAsync(user);
After signing in you can use the sign in manager to create the user principal and manually assign the HttpContext.User reference
This will then allow you to access the user id like you would with a normal signed in page
var userId = userManager.GetUserId(HttpContext.User);
Yes, as Anders said, User.Identity and User.IsInRole will not work inside the same login action.
So, You need to redirect to a new action, So inside login action add :
return RedirectToAction("MyNewLoginRoute", new {returnUrl=returnUrl });
below is a code example:
var result = SignInManager.PasswordSignIn(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
// below is the new line modification
return RedirectToAction("LoginRoute", new {returnUrl=returnUrl });
And Now add a new action LoginRoute as below :
// below method is new to get the UserId and Role
public ActionResult LoginRoute(string returnUrl) //this method is new
{
if (String.IsNullOrWhiteSpace(returnUrl))
{
if (User.IsInRole("Admin"))
{
return RedirectToLocal("/Admin");
}
else if (User.IsInRole("Partner"))
{
return RedirectToLocal("/Partner/Index/");
}
else if (User.IsInRole("EndUser"))
{
ApplicationDbContext db = new ApplicationDbContext();
// know the partner
int partnerID = db.Users.Where(x => x.UserName == User.Identity.Name).FirstOrDefault().PartnersTBLID;
return RedirectToLocal("/Partner/List/" + partnerID.ToString());
}
}
else
{
return RedirectToLocal(returnUrl);
}
}
Hope this could help someone.
I get the user doing the following right after sign-in:
var userId = SignInManager.AuthenticationManager.AuthenticationResponseGrant.Identity.GetUserId();
var user = SignInManager.UserManager.Users.Where(x => x.Id.Equals(userId)).FirstOrDefault();
For those who are implementing custom auth provider and still did not get it why User.Identity.GetUserId() is returning null after successful login
You just need to add ClaimType.NameIdentifier while adding a claim, see below line:
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Email));
Adding below code just for reference:
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
/*Following line(ClaimTypes.NameIdentifier) is the reason to return UserId for User.Identity.GetUserId()*/
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Email));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"UserName", user.Name
},
/*add more user info here*/
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
Here's what worked for me:
await SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false);
AuthenticationManager.User = new GenericPrincipal(AuthenticationManager.AuthenticationResponseGrant.Identity, null);
Once executed, you get authenticated state for the current request.
Finally, I found the solution.
Actually none of the above answers work for me.
I add a new project with identity to my solution.
Set the identity to my ApplicationUser.cs in my database Layer
add this to my startup.cs
Configuration.GetConnectionString("DefaultConnection")));
//services.AddDefaultIdentity<ApplicationUser>( options => options.SignIn.RequireConfirmedAccount = true )
// .AddEntityFrameworkStores<ApplicationDbContext>( );
services.AddIdentity < ApplicationUser,
IdentityRole > (options = >{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 4;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.SignIn.RequireConfirmedAccount = false;
}).AddEntityFrameworkStores < ApplicationDbContext > ().AddDefaultTokenProviders();```
it was hard to set and config all above but need to be brace and watchful to the operations. Try this, it works.

Email confirmation fails

I need to confirm the user's email by link but ASP.Net Identity fails on this action. Below the code is how I generate a token:
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id); // Return Opd+0yNG++ZXFpRS19A8j8OgI7dzCAKTYWotHYvuu0nyYsH4SIQS+bHwkbmqQDlwvGAy5fyxxUsu4yIzHwF+PD6QNPU+PwBnIDUGMC9++FkMlqegqHVKpA59qvbokfI0yByYqLoZfD1EUpWExddDN0BN/SVNSgOKlyzd928k+k4O2/TnfSf/JFj8x1NUKuaj
And how I try to check and confirm it:
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
Logger.Error(string.Format("userId: {0}, code: {1}", userId, code));
return View("Error");
}
// args values: userId: b96bf253-62e1-4c5e-bf5f-5bc527df9fd9 code: Opd 0yNG ZXFpRS19A8j8OgI7dzCAKTYWotHYvuu0nyYsH4SIQS bHwkbmqQDlwvGAy5fyxxUsu4yIzHwF PD6QNPU PwBnIDUGMC9 FkMlqegqHVKpA59qvbokfI0yByYqLoZfD1EUpWExddDN0BN/SVNSgOKlyzd928k k4O2/TnfSf/JFj8x1NUKuaj
var result = await UserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded) // This is false
{
return View("ConfirmEmail");
}
Logger.Error(string.Concat<string>(result.Errors)); // Wrong marker
return View("Error");
}
There are no null-values I checked in the debugger. Why can it fail?
P.S. I use Npgsql if it is important and all works perfect except this.
When the + sign is lost, it indicates an UrlEncoding issue. + represents a whitespace and if you want to preserve it, you should encode your code content and then decode it in your ConfirmEmail method right before calling UserManager.ConfirmEmailAsync.
When encoded, + becomes %2B and vice versa when decoded.
Use HttpUtility.UrlEncode() and HttpUtility.UrlDecode() from the System.Web namespace.
Firstly you must be determine token is correct or not.
On your generate token, note that token and check your database after that.
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
UserManager.EmailService = new EmailService();
await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
Also this is my email service for check.
public class EmailService : IIdentityMessageService
{
public System.Threading.Tasks.Task SendAsync(IdentityMessage message)
{
// Plug in your email service here to send an email.
var mail = new MailMessage
{
Subject = message.Subject,
Body = message.Body,
IsBodyHtml = true
};
mail.To.Add(message.Destination);
mail.From = new MailAddress("me#mail.com","me");
var smtp = new SmtpClient
{
Host = "smtp.mail.com",
Port = 25,
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential("me#mail.com", "password"),
EnableSsl = true
};
// Enter seders User name and password
smtp.Send(mail);
return System.Threading.Tasks.Task.FromResult(0);
}
When you get email check link, it must be match with callbackurl. When you click url this method should be call.
[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);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
In this method you must be see code variable is match with your code at your identity table.
Also there is important problem with Identity Framework. There is no dependency injection container or etc. on MVC out of the box. You must be create your own IoC. I always prefer structuremap to do this. Because lifetime manager better than others(ninject, unity etc.). Also I'm using current user object for reduce request to database or session manager.
public class CurrentUser : ICurrentUser
{
private readonly ApplicationDbContext _context;
private readonly IIdentity _identity;
private ApplicationUser _user;
public CurrentUser(IIdentity identity, ApplicationDbContext context)
{
_identity = identity;
_context = context;
}
public ApplicationUser User
{
get { return _user ?? (_user = _context.Users.Find(_identity.GetUserId())); }
}
}
In my MVC registry ( please check structuremap docs), i register everything that i need on
public class MvcRegistry : Registry
{
public MvcRegistry()
{
For<BundleCollection>().Use(BundleTable.Bundles);
For<RouteCollection>().Use(RouteTable.Routes);
For<IIdentity>().Use(() => HttpContext.Current.User.Identity);
For<IUserStore<ApplicationUser>>().Use<UserStore<ApplicationUser>>();
For<DbContext>().Use(() => new ApplicationDbContext());
For<IAuthenticationManager>().Use(() => HttpContext.Current.GetOwinContext().Authentication);
For<HttpSessionStateBase>().Use(() => new HttpSessionStateWrapper(HttpContext.Current.Session));
For<HttpContextBase>().Use(() => new HttpContextWrapper(HttpContext.Current));
For<HttpServerUtilityBase>().Use(() => new HttpServerUtilityWrapper(HttpContext.Current.Server));
}
}
With this I always use same object for same user also i prevent object reference problem and tight coupling problems as well.

ASP.NET MVC Identity login without password

I have been given the assignment of modifying an ASP.NET MVC application in such a way that navigating to myurl?username=xxxxxx automatically logs in user xxxxxx, without asking for a password.
I already made it very clear that this is a terrible idea for many security-related reasons and scenarios, but the people in charge are determined. The site would not be publicly available.
So: is there any way of signing in without a password by, for example, extending the Microsoft.AspNet.Identity.UserManager and modifying the AccountController?
Some code:
var user = await _userManager.FindAsync(model.UserName, model.Password);
if (user != null && IsAllowedToLoginIntoTheCurrentSite(user))
{
user = _genericRepository.LoadById<User>(user.Id);
if (user.Active)
{
await SignInAsync(user, model.RememberMe);
_userManager holds an instance of a Microsoft.AspNet.Identity.UserManager.
and SignInAsync():
private async Task SignInAsync(User user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await _userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
if (user.UserGroupId.IsSet())
user.UserGroup = await _userManager.Load<UserGroup>(user.UserGroupId);
//adding claims here ... //
AuthenticationManager.SignIn(
new AuthenticationProperties { IsPersistent = isPersistent },
new CustomClaimsIdentity(identity));
}
AuthenticationManager would be OwinSecurity.
You just need to use the usermanager to find the user by name. If you have a record then just sign them in.
public ActionResult StupidCompanyLogin()
{
return View();
}
[HttpPost]
//[ValidateAntiForgeryToken] - Whats the point? F**k security
public async Task<ActionResult> StupidCompanyLogin(string name)
{
var user = await UserManager.FindByNameAsync(name);
if (user != null)
{
await SignInManager.SignInAsync(user, true, true);
}
return View();
}
For those who receive an error from Mr. #heymega's post. The result keyword is missing. The short and correct answer is:
var user = UserManager.FindByNameAsync(model.UserName).Result;
try
{
await SignInManager.SignInAsync(user, isPersistent:
false,rememberBrowser:false) ;
return Json(new { success = true, message = "Success" });
}
catch (Exception ex)
{
return Json(new { success = false, message = "Wrong username" });
}

Categories

Resources