simpleinjector / SimpleInjector

An easy, flexible, and fast Dependency Injection library that promotes best practice to steer developers towards the pit of success.
https://simpleinjector.org
MIT License
1.21k stars 154 forks source link

ASP.NET MVC 5 : no IUserTokenProvider is registered when sending forgot password email #954

Closed aoldf2 closed 2 years ago

aoldf2 commented 2 years ago

I'm using ASP.Net Identity 2.2 and I'm unable to inject ApplicationUserManager into the AccountController. The UserManager properties are null. I believe the issue is with how I am registering UserManager and I'm at a loss of what to try. Any suggestions would be helpful.

AccountController:

private ApplicationUserManager _userManager;

public AccountController(ApplicationUserManager userManager, IUserAuditCommand userAuditCommand, IUserViewModelBuilder builder, IMapper mapper)
{
    UserManager = userManager;
    _userAuditCommand = userAuditCommand;
    _mapper = mapper;
    _builder = builder;
    _emailClient = new EmailClient();
}

public ApplicationUserManager UserManager
{
    get
    {
        return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
    }
    private set
    {
        _userManager = value;
    }
}

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindByNameAsync(model.Email);

        if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
        {
            ModelState.AddModelError("", "The user either does not exist or is not confirmed.");
            return View();
        }

         // Send an email with this link
         string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
         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 <a href=\"" + callbackUrl + "\">here</a>");
         return RedirectToAction("ForgotPasswordConfirmation", "Account");
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

ApplicationUserManager:

public class ApplicationUserManager : UserManager<User, long>
{
    public ApplicationUserManager(ApplicationUserStore store) : base(store) { }

    private const int PASSWORD_HISTORY_LIMIT = 5;

    public override async Task<IdentityResult> ChangePasswordAsync(long userId, string currentPassword, string newPassword)
    {
        if (await IsPreviousPassword(userId, newPassword))
        {
            return await Task.FromResult(IdentityResult.Failed("Cannot reuse old password"));
        }

        var result = await base.ChangePasswordAsync(userId, currentPassword, newPassword);

        if (result.Succeeded)
        {
            var store = Store as ApplicationUserStore;

            await store.AddToPreviousPasswordsAsync(await FindByIdAsync(userId), PasswordHasher.HashPassword(newPassword));
        }

        return result;
    }

    public override async Task<IdentityResult> ResetPasswordAsync(long userId, string token, string newPassword)
    {
        if (await IsPreviousPassword(userId, newPassword))
        {
            return await Task.FromResult(IdentityResult.Failed("Cannot reuse old password"));
        }

        var result = await base.ResetPasswordAsync(userId, token, newPassword);

        if (result.Succeeded)
        {
            var store = Store as ApplicationUserStore;

            await store.AddToPreviousPasswordsAsync(await FindByIdAsync(userId), PasswordHasher.HashPassword(newPassword));
        }

        return result;
    }

    private async Task<bool> IsPreviousPassword(long userId, string newPassword)
    {
        var store = Store as ApplicationUserStore;
        // List<PreviousPassword> passwords = await store.PreviousPasswords(userId);
        var user = await FindByIdAsync(userId);

        var query = user.PreviousPasswords.OrderByDescending(x => x.CreateDate).Take(PASSWORD_HISTORY_LIMIT);
        query = query.Where(x => PasswordHasher.VerifyHashedPassword(x.Password, newPassword) != PasswordVerificationResult.Failed);

        if (query.Any())
        {
            return true;
        }

        return false;
    }

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
    {
        var manager = new ApplicationUserManager(new ApplicationUserStore(context.Get<IdentityContext>()));

        manager.MaxFailedAccessAttemptsBeforeLockout = 3;
        manager.DefaultAccountLockoutTimeSpan = System.TimeSpan.FromMinutes(15);
        manager.UserLockoutEnabledByDefault = true;

        // Configure validation logic for usernames
        manager.UserValidator = new UserValidator<User, long>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = false,
        };

        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 8,
            RequireNonLetterOrDigit = false,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };

        // 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.
        manager.RegisterTwoFactorProvider(
            "PhoneCode",
            new PhoneNumberTokenProvider<User, long>
            {
                MessageFormat = "Your security code is: {0}"
            });
        manager.RegisterTwoFactorProvider(
            "EmailCode",
            new EmailTokenProvider<User, long>
            {
                Subject = "Security Code",
                BodyFormat = "Your security code is: {0}"
            });
        manager.EmailService = new EmailService();
        //manager.SmsService = new SmsService();
        var dataProtectionProvider = options.DataProtectionProvider;

        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider = new DataProtectorTokenProvider<User, long>(dataProtectionProvider.Create("ASP.NET Identity"));
        }

        return manager;
    }
}

SimpleInjectorInitializer:

container.Register<HttpContextBase>(() => new HttpContextWrapper(HttpContext.Current), Lifestyle.Scoped);
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.Register(() => new ApplicationUserManager(new ApplicationUserStore(container.GetInstance<IdentityContext>())), Lifestyle.Scoped);

Error Message:

No IUserTokenProvider is registered.
Description: An unhandled exception occurred during the execution of the 
current web request. Please review the stack trace for more information 
about the error and where it originated in the code.

Exception Details: System.NotSupportedException: No IUserTokenProvider is registered.

Source Error (Line 274 highlighted):
Line 272:                // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
Line 273:                // Send an email with this link
Line 274:                 string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
Line 275:                 var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);        
Line 276:                 await UserManager.SendEmailAsync(user.Id, "Reset Password", "Please reset your password by clicking <a href=\"" + callbackUrl + "\">here</a>");

    Stack Trace:
    [NotSupportedException: No IUserTokenProvider is registered.]
   Microsoft.AspNet.Identity.<GenerateUserTokenAsync>d__127.MoveNext() +639
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +102    System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +64
       System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +29
       ILETSB.OfficerPortal.UI.Controllers.<ForgotPassword>d__21.MoveNext() in C:\Users\aoldfield\source\repos\ILETSB.OfficerPortal\ILETSB.OfficerPortal.UI\Controllers\AccountController.cs:274
       System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +102
       System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +64
       System.Web.Mvc.Async.TaskAsyncActionDescriptor.EndExecute(IAsyncResult asyncResult) +97
       System.Web.Mvc.Async.<>c__DisplayClass8_0.<BeginInvokeAsynchronousActionMethod>b__1(IAsyncResult asyncResult) +17
       System.Web.Mvc.Async.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult) +10
       System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
       System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +32
       System.Web.Mvc.Async.<>c__DisplayClass11_0.<InvokeActionMethodFilterAsynchronouslyRecursive>b__0() +58
       System.Web.Mvc.Async.<>c__DisplayClass11_2.<InvokeActionMethodFilterAsynchronouslyRecursive>b__2() +228
       System.Web.Mvc.Async.<>c__DisplayClass7_0.<BeginInvokeActionMethodWithFilters>b__1(IAsyncResult asyncResult) +10
       System.Web.Mvc.Async.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult) +10
       System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
       System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +34
       System.Web.Mvc.Async.<>c__DisplayClass3_6.<BeginInvokeAction>b__4() +35
       System.Web.Mvc.Async.<>c__DisplayClass3_1.<BeginInvokeAction>b__1(IAsyncResult asyncResult) +100
       System.Web.Mvc.Async.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult) +10
       System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
       System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +27
       System.Web.Mvc.<>c.<BeginExecuteCore>b__152_1(IAsyncResult asyncResult, ExecuteCoreState innerState) +11
       System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +29
       System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
       System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +45
       System.Web.Mvc.<>c.<BeginExecute>b__151_2(IAsyncResult asyncResult, Controller controller) +13
       System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +22
       System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
       System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +26
       System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +10
       System.Web.Mvc.<>c.<BeginProcessRequest>b__20_1(IAsyncResult asyncResult, ProcessRequestState innerState) +28
       System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +29
       System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
       System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +28
       System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
       System.Web.CallHandlerExecutionStep.InvokeEndHandler(IAsyncResult ar) +161
       System.Web.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar) +128
dotnetjunkie commented 2 years ago

This is a duplicate.