Search code examples
asp.net-identityidentityserver4

What is the right way for implement and authenticate Db Users in IS4 instead of Test Users?


I'm working on microservices and implementing Hybrid flow. My project is working fine with Test Users. But I need to make it more dynamic and authenticate my DB users instead of Test Users.

I'm sharing my authentication project startup.cs file code here, which I've tried yet:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));

        services.AddControllersWithViews();

        services.AddIdentity<User, Role>()
            .AddRoles<Role>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddConfigurationStore(options =>
            {
                options.ConfigureDbContext = builder =>
                    builder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                        sql => sql.MigrationsAssembly(migrationsAssembly));
            })
            // this adds the operational data from DB (codes, tokens, consents)
            .AddOperationalStore(options =>
            {
                options.ConfigureDbContext = builder =>
                    builder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                        sql => sql.MigrationsAssembly(migrationsAssembly));
            });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseStaticFiles();
        app.UseRouting();
        app.UseIdentityServer();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapDefaultControllerRoute();
        });
    }

After running my above code I'm unable to login into my authentication server

enter image description here

No error is showing on the console window

enter image description here

Also same in the browser console

enter image description here

What if I run the below code is working fine and system log me in

public void ConfigureServices(IServiceCollection services)
    {

        services.AddControllersWithViews();

        var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddTestUsers(Config.TestUsers)
            .AddConfigurationStore(options =>
            {
                options.ConfigureDbContext = builder =>
                    builder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                        sql => sql.MigrationsAssembly(migrationsAssembly));
            })
            // this adds the operational data from DB (codes, tokens, consents)
            .AddOperationalStore(options =>
            {
                options.ConfigureDbContext = builder =>
                    builder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                        sql => sql.MigrationsAssembly(migrationsAssembly));
            });
    }

enter image description here

What I'm missing in the above code? what is the right way to authenticate the user from database instead of using Test Users?


Solution

  • I've found my question answer but it's strange that Identity Server 4 official docs not provided any documentation for switching Test Users to Actual User and tweaking at UI level.

    Answer is:

    Modify the contents of the login page

    At Quickstart/Account/AccountController.cs, you can see that the TestUser is injected in the constructor. Delete it and inject the following:

    Replace below:

    private readonly TestUserStore _users;
    

    With this:

    private readonly UserManager<User> _userManager;
    private readonly SignInManager<User> _signInManager;
    

    in constructor, replace below:

    IIdentityServerInteractionService interaction,
                IClientStore clientStore,
                IAuthenticationSchemeProvider schemeProvider,
                IEventService events,
                TestUserStore users = null)
            {
                _users = users ?? new TestUserStore(TestUsers.Users);
                _interaction = interaction;
                _clientStore = clientStore;
                _schemeProvider = schemeProvider;
                _events = events;
            }
    

    With this:

    public AccountController(
                UserManager<User> userManager,
                SignInManager<User> signInManager,
                IIdentityServerInteractionService interaction,
                IClientStore clientStore,
                IAuthenticationSchemeProvider schemeProvider,
                IEventService events)
            {
                _userManager = userManager;
                _signInManager = signInManager;
                _interaction = interaction;
                _clientStore = clientStore;
                _schemeProvider = schemeProvider;
                _events = events;
            }
    

    Find Login post action, Modify the login validation logic.

    if (ModelState.IsValid)
    {
        var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberLogin, lockoutOnFailure: true);
        if (result.Succeeded)
        {
            var user = await _userManager.FindByNameAsync(model.Username);
            await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName));
    
            if (context != null)
            {
                if (await _clientStore.IsPkceClientAsync(context.Client.ClientId))
                {
                    // if the client is PKCE then we assume it's native, so this change in how to
                    // return the response is for better UX for the end user.
                    return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
                }
    
                // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
                return Redirect(model.ReturnUrl);
            }
    
            // request for a local page
            if (Url.IsLocalUrl(model.ReturnUrl))
            {
                return Redirect(model.ReturnUrl);
            }
            else if (string.IsNullOrEmpty(model.ReturnUrl))
            {
                return Redirect("~/");
            }
            else
            {
                // user might have clicked on a malicious link - should be logged
                throw new Exception("invalid return URL");
            }
        }
    
        await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials"));
        ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
    }
    

    At Quickstart/Extensions.cs append following code

    public static async Task<bool> IsPkceClientAsync(this IClientStore store, string client_id)
    {
        if (!string.IsNullOrWhiteSpace(client_id))
        {
            var client = await store.FindEnabledClientByIdAsync(client_id);
            return client?.RequirePkce == true;
        }
    
        return false;
    }
    

    Original Answer with old modifications:

    In Identity Server 4, how to add actual users, not test users