Search code examples
asp.net-coreasp.net-identityidentityserver4

IdentityServer4 w/AspNetIdentity and Registration API Methods


I've setup IdentityServer4, a protected API (Core) project, and various clients. Anonymously accessed client pages use the Resource Owner flow to access the APIs and user credentials are used from client pages where login is required. This is all working. My problem is now I want to add registration API methods that are protected.

The new registration methods require the API project to use AspNetIdentity. Specifically they use Identity's UserManager object which is failing to instantiate unless I add this code to my Startup.ConfigureServices:

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

But adding this code breaks the normal IDServer4 Bearer authentication. The Authorize tag on the API controller now sends the requesters to the login page. Is there a way to create a good userManager component without the chunk of code above so Identity authentication does not come into play?

Without the code above I get the following error:

Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[TestAPICore.Models.ApplicationUser]' while attempting to activate *controller*

Here is my ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;

                options.ApiName = "api1";
            });

        services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
        {
            builder.AllowAnyOrigin()
                   .AllowAnyMethod()
                   .AllowAnyHeader();
        }));

        //// ADDING THIS CAUSES API requests to send back the login screen
        //services.AddIdentity<ApplicationUser, IdentityRole>()
        //    .AddEntityFrameworkStores<ApplicationDbContext>()
        //    .AddDefaultTokenProviders();

        // Add application services.
        services.AddTransient<IEmailSender, EmailSender>();

        services.AddMvc();
    }

Ideas for making this work?

Update:

Reading more, it looks like calling services.AddIdentityCore<ApplicationUser>(cfg => {}); is the way to go. I've tried it both before and after the .AddAuthentication code but I still get nearly the same error:

System.InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore`1[TestAPICore.Models.ApplicationUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.UserManager`1[TestAPICore.Models.ApplicationUser]'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites...

...which is slightly different since it no longer references my controller.

Solved!

Here's what worked...

IdentityBuilder builder = services.AddIdentityCore<ApplicationUser>(options => { });
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>();

Thanks,Kirk, for pointing me in the right direction!


Solution

  • So that others may benefit, here's my whole ConfigureServices method for my API project which can manage users but still authenticates against IdentityServer:

            public void ConfigureServices(IServiceCollection services)
            {
                services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
    
                services.AddDbContext<ApplicationDbContext>(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    
    
                services.AddAuthentication("Bearer")
                    .AddIdentityServerAuthentication(options =>
                    {
                        options.Authority = "http://localhost:5000";
                        options.RequireHttpsMetadata = false;
    
                        options.ApiName = "api1";
                    });
    
                services.AddCors(o => o.AddPolicy("MyPolicy", bld =>
                {
                    bld.AllowAnyOrigin()
                           .AllowAnyMethod()
                           .AllowAnyHeader();
                }));
    
                // *** The Fix ***
                IdentityBuilder builder = services.AddIdentityCore<ApplicationUser>(options => { });
                builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
                builder.AddEntityFrameworkStores<ApplicationDbContext>();
    
                // Add application services.
                services.AddTransient<IEmailSender, EmailSender>();
    
                services.AddMvc();
            }