Ch3shire
Ch3shire

Reputation: 1106

Add custom authentication method to IdentityServer4

I have basic setup of IdentityServer4 on ASP.NET Core 3 - basically, we have simple registration method and whole OAuth 2.0 protocol implemented. For now user can get authorization token from /connect/token, and server does somehow whole process of authentication using database.

My question is - how to customize process of authentication? How to grant access, for example, to users only with "admin" in username, or to users with passwords from only "p" letter, or born in 68', or only with active folder on server?

My Startup.cs code:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace AuthServer
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

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

            services.AddIdentity<AppUser, IdentityRole>(
                    options =>
                    {
                        options.Password.RequiredLength = 6;
                        options.Password.RequireLowercase = true;
                        options.Password.RequireDigit = false;
                        options.Password.RequireNonAlphanumeric = false;
                        options.Password.RequireUppercase = false;
                    }
                )
                .AddEntityFrameworkStores<AppUserDbContext>()
                .AddDefaultTokenProviders();


            services.AddIdentityServer()
                .AddInMemoryIdentityResources(Config.Ids)
                .AddInMemoryApiResources(Config.Apis)
                .AddInMemoryClients(Config.Clients)
                .AddDeveloperSigningCredential()
                .AddAspNetIdentity<AppUser>();


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

            services.AddControllers();
        }

        // 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.UseCors(options => options.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
            app.UseIdentityServer();

            app.UseRouting();

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

Upvotes: 1

Views: 1373

Answers (1)

Mehrdad
Mehrdad

Reputation: 1723

You can implement IResourceOwnerPasswordValidator interface for this purpose. then register it in your startup.cs

services.AddIdentityServer()
.AddResourceOwnerValidator<**PasswordAuthentication**>()

for more information read Resource Owner Password Validation please.

This is default implementation, so you can get idea and implement your owns.

public virtual async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        var clientId = context.Request?.Client?.ClientId;
        var user = await _userManager.FindByNameAsync(context.UserName);
        if (user != null)
        {
            var result = await _signInManager.CheckPasswordSignInAsync(user, context.Password, true);
            if (result.Succeeded)
            {
                var sub = await _userManager.GetUserIdAsync(user);

                _logger.LogInformation("Credentials validated for username: {username}", context.UserName);
                await _events.RaiseAsync(new UserLoginSuccessEvent(context.UserName, sub, context.UserName, false, clientId));

                context.Result = new GrantValidationResult(sub, AuthenticationMethods.Password);
                return;
            }
            else if (result.IsLockedOut)
            {
                _logger.LogInformation("Authentication failed for username: {username}, reason: locked out", context.UserName);
                await _events.RaiseAsync(new UserLoginFailureEvent(context.UserName, "locked out", false, clientId));
            }
            else if (result.IsNotAllowed)
            {
                _logger.LogInformation("Authentication failed for username: {username}, reason: not allowed", context.UserName);
                await _events.RaiseAsync(new UserLoginFailureEvent(context.UserName, "not allowed", false, clientId));
            }
            else
            {
                _logger.LogInformation("Authentication failed for username: {username}, reason: invalid credentials", context.UserName);
                await _events.RaiseAsync(new UserLoginFailureEvent(context.UserName, "invalid credentials", false, clientId));
            }
        }
        else
        {
            _logger.LogInformation("No user found matching username: {username}", context.UserName);
            await _events.RaiseAsync(new UserLoginFailureEvent(context.UserName, "invalid username", false, clientId));
        }

        context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
    }

Upvotes: 1

Related Questions