Search code examples
c#asp.net-web-apiasp.net-identityautofacidentityserver3

IdentityServer3, Autofac, IdentityFramework in existing project


I have been playing around with IdentityServer3 with the hopes to replace our current authentication process. Currently we use a custom identity framework process using code first entity framework. I managed to install IdentityServer3 and get the "in memory" stuff working. Now I want to hook it up to our already customised UserProvider (UserManager if you like).

We already use Autofac and have our UserProvider registered like this:

builder.RegisterType<UserProvider>().As<IUserProvider>().InstancePerDependency();

I found some documentation that states that IdentityServer uses Autofac itself. They recommend creating a factory and then using IdentityServerOptions to register the user service like this:

options.Factory.UserService = new Registration<IUserService>(UserServiceFactory.Create())

The problem I have with that, is the factory looks something like this:

public class UserServiceFactory
{
    public static AspNetIdentityUserService<User, string> Create()
    {
        var context = new IdentityDbContext();
        var userStore = new UserStore<User>(context);
        var userManager = new UserManager<User>(userStore);

        return new AspNetIdentityUserService<User, string>(userManager);
    }
}

Which is using the normal UserManager rather than our customised version and it isn't using DI because you create it all in the static method. Surely it would be better to use Autofac as we already have our UserProvider registered. So, I didn't use their IdentityServerOptions to invoke the static method. So I changed my factory to this:

public class IdentityServerUserService : UserServiceBase
{
    private readonly IUserProvider _userProvider;

    public IdentityServerUserService(IUserProvider userProvider)
    {
        _userProvider = userProvider;
    }

    public override async Task AuthenticateLocalAsync(LocalAuthenticationContext context)
    {
        var user = await _userProvider.FindAsync(context.UserName, context.Password);

        if (user != null && !user.Disabled)
        {
            // Get the UserClaims

            // Add the user to our context
            context.AuthenticateResult = new AuthenticateResult(user.Id, user.UserName, new List<Claim>());
        }
    }
}

Which I registered in autofac like this:

builder.RegisterType<IdentityServerUserService>()
       .As<IdentityServer3.Core.Services.IUserService>()
       .InstancePerDependency();

And then I assigned to the IdentityServerOptions.Factory.UserService like this:

private static void SetupServices(IdentityServerOptions options, ILifetimeScope scope)
{
    options.Factory.UserService = new Registration<IUserService>(scope.Resolve<IdentityServerUserService>());
}

And the scope I get like this:

var config = new HttpConfiguration();
var scope = config.DependencyResolver.GetRootLifetimeScope(); 

I believe this should work, but I get an error when I try to use postman to authenticate:

Autofac.Core.Registration.ComponentNotRegisteredException: The requested service 'Business.IdentityServerUserService' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.

I tried to change from InstancePerDependency to InstancePerLifetimeScope but still got the same error.

So, I have a couple of questions:

  • Is this the right way to assign the UserService?
  • Will this allow my existing users to authenticate?
  • Has anyone done this before? If so, did they get it to work?

If anyone can help me with these questions, I would be eternally grateful.


Solution

  • You resolve IdentityServerUserService but you register IdentityServerUserService as IUserService. Autofac doesn't automatically register the type as itself.

    To fix the error you can register the type as itself

    builder.RegisterType<IdentityServerUserService>()
           .As<IdentityServer3.Core.Services.IUserService>()
           .InstancePerDependency();
    

    or resolve IUserService

    options.Factory.UserService = new Registration<IUserService>(scope.Resolve<IUserService>())