Email confirmation fails - c#

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.

Related

ASP MVC ConfirmEmailAsync not working

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

Pass HttpPostedFileBase object in asp.net Identity action method and validating with face api

Hello guys i try to implement a registration functionality using asp.net identity.
One of my required properties is the new user's photo which i pass as an argument in a action method where finally i call faceServiceClient.DetectAsync() method of azure face api.
I grab the photo using a <input type='file'> in my view
and then in account controller of identity in register action method i use a HttpPostedFileBase object to read it.
The problem is that faceServiceClient.DetectAsync() needs as first argument a stream object or a string(imagePath) but in my case i cant figure it out how i can give a stream object or the image path
HttpPostedFileBase doesn't retrieve the image path but to get the image i need this type of object.
And then even if i try to pass httpPostedFilebase object.InputStream as argument i get null by break pointing the await faceServiceClient.DetectAsync command line
To be specific
Account Controller
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register([Bind(Exclude = "UserPhoto")]RegisterViewModel model, HttpPostedFileBase userPhoto)
{
if ((userPhoto==null) || (userPhoto.ContentLength <= 0))
{
ModelState.AddModelError("error", #"Please Select a profile picture");
}
if (ModelState.IsValid)
{
var faceApiresult = await new FaceRecognitionController().GetDetectedFaces(userPhoto);
if (!faceApiresult)
{
ModelState.AddModelError("error", #"Your picture does not include your face");
return RedirectToAction("Index", "Home");
}
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
UName = model.Username,
UserPhoto = model.UserPhoto
};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// For more information on how to enable account confirmation and password reset please visit https://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");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
and my FaceRecognitionController
public class FaceRecognitionController : Controller
{
private static string ServiceKey = ConfigurationManager.AppSettings["FaceServiceKey"];
private static string EndPoint = ConfigurationManager.AppSettings["FaceServiceEndPoint"];
[HttpGet]
public async Task<dynamic> GetDetectedFaces(HttpPostedFile userPhoto)
{
var photo = new byte[userPhoto.ContentLength];
if (userPhoto.ContentLength == 0) return false;
try
{
// Create Instance of Service Client by passing Servicekey as parameter in constructor
var faceServiceClient = new FaceServiceClient(ServiceKey);
var faces = await faceServiceClient.DetectAsync(userPhoto.InputStream, true, true, new FaceAttributeType[] { FaceAttributeType.Gender, FaceAttributeType.Age, FaceAttributeType.Smile, FaceAttributeType.Glasses });
//check if find any faces
var results = faces.Length;
return results != 0;
}
catch (FaceAPIException)
{
//do exception work
}
return false;
}
}
}
As you can see i just check if validation api find any face and just return true to complete the registration
any thoughts on how i can overcome this??
Ok after some more searching i found this article
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads
in which i read that i can use this line of code
var filePath = Path.GetTempFileName();
and get the temp file path .So after that i successfully added it as a parameter to the action method and then in faceServiceClient.DetectAsync
and use tis path as first argument and worked

Using UserManager.FindAsync with a custom UserStore

I have implemented a custom UserStore, it implements IUserStore<DatabaseLogin, int> and IUserPasswordStore<DatabaseLogin, int>.
My Login action method is as below:
if (ModelState.IsValid)
{
if (Authentication.Login(user.Username, user.Password))
{
DatabaseLogin x = await UserManager.FindAsync(user.Username, user.Password);
DatabaseLogin Login = Authentication.FindByName(user.Username);
if (Login != null)
{
ClaimsIdentity ident = await UserManager.CreateIdentityAsync(Login,
DefaultAuthenticationTypes.ApplicationCookie);
AuthManager.SignOut();
AuthManager.SignIn(new AuthenticationProperties
{
IsPersistent = false
}, ident);
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "Invalid Login");
}
}
return View();
In the custom authentication class that I wrote, Authentication, I have a Login method that works fine, also FindByName method returns an app user. But if I try to SignIn with that login, the user isn't recognized as authenticated and HttpContext.User.Identity is always null, so I imagine that I have to try UserManager.FindAsync.
This method calls FindByNameAsync and GetPasswordHashAsync, and it always return null.
public Task<DatabaseLogin> FindByNameAsync(string userName)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName");
return Task.FromResult<DatabaseLogin>(Authentication.FindByName(userName));
}
public Task<string> GetPasswordHashAsync(DatabaseLogin user)
{
if (user == null)
throw new ArgumentNullException("user");
return Task.FromResult<string>(user.Password);
}
And the Authentication.FindByName
public static DatabaseLogin FindByName(string name)
{
string GetUserQuery = string.Format(
"USE db;SELECT principal_id AS id, name as userName, create_date AS CreateDate, modify_date AS modifyDate FROM sys.database_principals WHERE type='S' AND authentication_type = 1 AND name = '{0}'"
, name);
DatabaseLogin user;
using (var db = new EFDbContext())
{
user = db.Database.SqlQuery<DatabaseLogin>(GetUserQuery).FirstOrDefault();
}
user.Password = Convert.ToBase64String(Encoding.ASCII.GetBytes("pass"));
return user;
}
As you can see I'm using database users, I'm not sure how I can retrieve a hashed password for them. For now, I'm just storing the Base65 of the correct password!
I have no idea where I'm going wrong, any guidance is welcome.
Short answer: nothing's wrong. User is authenticated in other action methods, but apparently not in the current action method.
This is the process that I followed, maybe it will help you debug your app.
After reading the source code, FindAsync first calls FindByNameAsync, followed by CheckPasswordAsync which references VerifyPasswordAsync. So it should be fine If I could override VerifyPasswordAsync.
I created a custom password hasher that implements IPasswordHasher, and registered it in the create method of my UserManager like this:
manager.PasswordHasher = new DbPasswordHasher();
So by now, I can get my user from UserManager.FindAsync, but it turned out that it doesn't matter where you get the user since HttpContext.User.Identity is still null! My mistake was that I didn't notice the user isn't authenticated in the current action, in other action methods it works as expected!

"Invalid token" error while reset password, ASP.Net Identity 2.2.0

I'm developing a MVC5 ASP.Net application.
I'm using Identity 2.2.0 for authentication.
Everything is OK, but I can't reset my password, because of Invalid Token error.
The following are ResetPassword related actions in Account controller.
[AllowAnonymous]
public ActionResult ForgotPassword()
{
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (!ModelState.IsValid) return View(model);
ApplicationUser userModel = await UserManager.FindByNameAsync(model.Email);
if (userModel == null)
ModelState.AddModelError("", "The user doesn't exist");
if (userModel != null && !await UserManager.IsEmailConfirmedAsync(userModel.Id))
ModelState.AddModelError("", "The user email isn't confirmed");
if (!ModelState.IsValid) return View();
var user = _userService.GetUserByEmail(model.Email);
// 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 websiteTitle = StaticAssests.WebsiteTitle;
string emailContext = _settingService.GetValue(SettingNames.ResetPasswordMailFormat);
string code = await UserManager.GeneratePasswordResetTokenAsync(userModel.Id);
string callbackUrl = Url.Action("ResetPassword", "Account", new { userId = userModel.Id, code }, Request.Url.Scheme);
emailContext = emailContext.Replace("{userfullname}", user.FullName);
emailContext = emailContext.Replace("{websitetitle}", websiteTitle);
emailContext = emailContext.Replace("{websitedomain}", StaticVariables.WebsiteDomain);
emailContext = emailContext.Replace("{username}", userModel.UserName);
emailContext = emailContext.Replace("{resetpasswordurl}", callbackUrl);
emailContext = emailContext.Replace("{date}", new PersianDateTime(DateTime.Now).ToLongDateTimeString());
await UserManager.SendEmailAsync(userModel.Id, string.Format("Reset password {0}", websiteTitle), emailContext);
return RedirectToAction("ForgotPasswordConfirmation", "Account");
}
[AllowAnonymous]
public ActionResult ForgotPasswordConfirmation()
{
return View();
}
[AllowAnonymous]
public ActionResult ResetPassword(string code)
{
return code == null ? View("Error") : View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid) return View(model);
ApplicationUser userModel = await UserManager.FindByNameAsync(model.Email);
if (userModel == null)
{
ModelState.AddModelError("", "The user doesn't exist");
return View();
}
// Invalid Token error
IdentityResult result = await UserManager.ResetPasswordAsync(userModel.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
AddErrors(result);
return View();
}
I've checked the followings:
1. Resetting email send successfully.
2. GeneratePasswordResetTokenAsync run without any problem. and the generated code with it is the same with code argument in ResetPassword action.
IdentityConfig:
public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
public ApplicationUserManager(IUserStore<ApplicationUser, int> userStore) : base(userStore)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
ApplicationUserManager applicationUserManager = new ApplicationUserManager(new ApplicationUserStore());
//ApplicationUserManager applicationUserManager = new ApplicationUserManager(context.Get<ApplicationUser>());
//new ApplicationUserManager(new UserStore<UserModel>(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
//applicationUserManager.UserValidator = new UserValidator<UserIdentityModel, int>(applicationUserManager)
//{
// AllowOnlyAlphanumericUserNames = false,
// RequireUniqueEmail = true,
//};
applicationUserManager.PasswordValidator = new MyMinimumLengthValidator(6);
applicationUserManager.UserValidator = new MyUserModelValidator();
applicationUserManager.PasswordHasher = new MyPasswordHasher();
// Configure user lockout defaults
applicationUserManager.UserLockoutEnabledByDefault = true;
applicationUserManager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
applicationUserManager.MaxFailedAccessAttemptsBeforeLockout = 5;
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug in here.
applicationUserManager.RegisterTwoFactorProvider("PhoneCode",
new PhoneNumberTokenProvider<ApplicationUser, int>
{
MessageFormat = "Your security code is: {0}"
});
applicationUserManager.RegisterTwoFactorProvider("EmailCode",
new EmailTokenProvider<ApplicationUser, int>
{
Subject = "Security code",
BodyFormat = "your security code is {0}"
});
applicationUserManager.EmailService = new EmailService();
applicationUserManager.SmsService = new SmsService();
IDataProtectionProvider dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
applicationUserManager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser, int>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return applicationUserManager;
}
}
What's wrong?
Update:
I've changed User Id from string to int.
I know this is an old question, but I've run into this on two projects and both times the issue was the exact same thing. Perhaps this answer might help others. The token being passed includes special characters that cannot be used as is within a URL string without causing problems.
When passing the token to the front end UI, be sure to URLEncode the token, something like this:
var callbackUrl =
new Uri(string.Format("{0}/resetpassword?userId={1}&code={2}",
ConfigurationManager.AppSettings["websiteUrl"], user.Id,
WebUtility.UrlEncode(token)));
When the token is passed back into the back end, Decode the token before passing it to the password ResetPassword function:
var result = await this.AppUserManager.ResetPasswordAsync(appUser.Id, WebUtility.UrlDecode(resetPasswordBindingModel.ConfirmCode),
resetPasswordBindingModel.NewPassword);
Both of the projects where I had this issue were running HTML/MVC/JavaScript on the front end and the validations were being done over ASP.NET WebAPI 2.x.
One other note: UrlDecode, for some reason, can't properly decode the plus symbol and you get a space in the token instead of the '+'. The trick I used is to just use a string replace to convert any spaces to + signs. It's not ideal, but I've not had a problem with it.

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