Search code examples
c#asp.netasp.net-mvcasp.net-identity

ASP.Net identity 2 with windows service and MVC, Token issues


I have a windows service, a MVC application and a Webforms web application in the same solution. From Webforms I am trying to send a password confirm e-mail to a user. The Webforms app sends a message through MSMQ to the Windows Service (message consumers). The Windows Service then creates a user, their confirmation token and sends the e-mail.

The user when clicking the link then confirms their e-mail on the MVC application. All this works fine on me development machine.

Whenever I deploy on production I start getting invalid token issues.

Some relevant code in my setup:

In the Windows Service:

Autofac config

builder.Register(c => new UserManager<User, long>(new UserStore(new DbEntities()))).InstancePerLifetimeScope();

Inside the method that creates the token and e-mail

Constructor

    private readonly UserManager<User, long> _usermanager;
    private readonly IUnitOfWork _uow;
    public ConfiguratorUserService(IUnitOfWork uow)
    {
        _uow = uow;
        _usermanager = new UserManager<User, long>(new UserStore((DeronEntities)_uow.Context));
        var provider = new DpapiDataProtectionProvider("DeronConfigurator");
        _usermanager.UserTokenProvider = new DataProtectorTokenProvider<User, long>(provider.Create("Passwords"));
    }

Method

 var user = new User
 {
     ... more props setting
     UserName = contact.Emailadres,
 };
 _usermanager.Create(user);
 var provider = new DpapiDataProtectionProvider("Configurator");
 _usermanager.UserTokenProvider = new DataProtectorTokenProvider<User, long>(provider.Create("Passwords"));
 var token = _usermanager.GenerateEmailConfirmationToken(user.Id);
 var data = Encoding.UTF8.GetBytes(token);
 var base64EncodedToken = System.Convert.ToBase64String(data);
 // And sending the e-mail here

Then on the MVC side I have

Autofac

builder.Register(c => new UserManager<User, long>(new UserStore(new DeronEntities()))).InstancePerLifetimeScope();

Auth setup (startup)

var provider = new DpapiDataProtectionProvider("Configurator");

app.CreatePerOwinContext(() => new UserManager(new UserStore(new DeronEntities()))
{
     UserTokenProvider = new DataProtectorTokenProvider<User, long>(provider.Create("Passwords"))
});

AccountController

var data = Convert.FromBase64String(code);
var base64Decoded = Encoding.UTF8.GetString(data);

var result = await UserManager.ConfirmEmailAsync(userId, base64Decoded);

What I have tried and found out":

  • It is working fine on localhost (dev environment)
  • I have tried many suggestions from So and other resource listed below
  • The token itself is fine when I log on the Windows Service and AccountController. They match when logging them
  • I checked naming of DataProtectionProvider AppName and Purposes
  • I am base 64 encoding tokens, so no issue with url encoding etc there, as stated on many SO answers
  • The only true difference I can see for now is that production runs on SSL and localhost doesn't
  • Production environment has all apps on the same machine, no load balancer setup or things like that

Tried solutions: http://www.gunaatita.com/blog/Invalid-Token-Error-on-Email-Confirmation-in-Aspnet-Identity/1056

MachineKeyDataProtector - Invalid link when confirmation email sent through background job

I am unsure how to setup machine key usage for the Windows Service


Solution

  • I have resolved the issue. It is by using machine key setup.

    Relevant posts that helped me solve it:

    MachineKeyDataProtector - Invalid link when confirmation email sent through background job

    Generating reset password token does not work in Azure Website

    So my setup now looks like this:

    In Windows Service

    Autofac config:

    builder.Register<IUserTokenProvider<User, long>>(c => DataProtector.TokenProvider).InstancePerLifetimeScope();
    
    builder.RegisterType<UserManager<User, long>>()
                    .As<UserManager<User, long>>().InstancePerLifetimeScope(); 
    

    And in the constructor:

        private readonly IUnitOfWork _uow;
        private readonly UserManager<User, long> _usermanager;
    
        public ConfiguratorUserService(UserManager<User, long> userManager, IUnitOfWork uow)
        {
            _uow = uow;
            _usermanager = userManager;
        }
    

    In MVC

    Autofac

            builder.Register(c => new UserManager<User, long>(new UserStore(new DeronEntities()))).InstancePerLifetimeScope();
    
            builder.Register<IUserTokenProvider<User, long>>(c => DataProtector.TokenProvider).InstancePerRequest();
    

    I have taken the MachineKeyProtectionProvider from the linked posts

    Add in your web.config the machine key:

      <system.web>
          <machineKey decryption="AES" decryptionKey="dec key here" validation="HMACSHA256" validationKey="key here"/>
      </system.web>
    

    And add the same in the app.config of the widows service