Tiago Crizanto
Tiago Crizanto

Reputation: 312

Net Core 3.1 API with Identity Server 4 custom password validation

I'm building an API using identity server and I need to use an existing database. The users password are stored with a custom hash password. I use FindClientByIdAsync to validate user and password, but as the password is encrypted in a non-standard algorithm I get invalid_client error message. If I change in execution time (with breakpoint) the value of password for an unencrypted value the authentication works. It's possible change the client_secret validation for FindClientByIdAsync?

Custom ClientStore class

public class ClientStore : IClientStore
{
    private readonly IMyUserRepository myUserRepository;

    public ClientStore(IMyUserRepository myUserRepository)
    {
        this.myUserRepository = myUserRepository;
    }

    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId()
        };
    }

    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource("My_API", "My API")
        };
    }

    public async Task<Client> FindClientByIdAsync(string client)
    {
        var user = await myUserRepository.GetUserByEmailAsync(client);

        if (user == null)
            return null;

        return new Client()
        {
            ClientId = client,
            AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
            ClientSecrets =
            {
                new Secret(user.Password.Sha256()) //if I change to unencrypted works, but the value in database is hashed
            },
            AllowedScopes = { "GOLACO_API", IdentityServerConstants.StandardScopes.OpenId }
        };
    }
}

Identity server configuration in Startup class

services.AddIdentityServer(options =>
            {
                options.Events.RaiseSuccessEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseErrorEvents = true;
            })
            .AddSigningCredential(GetSigningCredential()) // here I just read the private.key file
            .AddInMemoryIdentityResources(ClientStore.GetIdentityResources())
            .AddInMemoryApiResources(ClientStore.GetApiResources())
            .AddClientStore<ClientStore>();

services.AddAuthentication("Bearer")
              .AddIdentityServerAuthentication(options =>
              {
                  options.Authority = configuration["Configuration"];
                  options.ApiName = "My_API";
                  options.RequireHttpsMetadata = false;
              });

        services.AddAuthentication()
            .AddFacebook("Facebook", options =>
            {
                options.AppId = "1234";
                options.AppSecret = "1234567890";
            });

        var policy = new AuthorizationPolicyBuilder()
               .RequireAuthenticatedUser()
               .Build();

Upvotes: 2

Views: 1022

Answers (1)

Kishan Vaishnav
Kishan Vaishnav

Reputation: 2631

You have to implement IResourceOwnerPasswordValidator as Damien showed in his blog

public class CustomResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
    private readonly IUserRepository _userRepository;

    public CustomResourceOwnerPasswordValidator(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        if (_userRepository.ValidateCredentials(context.UserName, context.Password))
        {
            var user = _userRepository.FindByUsername(context.UserName);
            context.Result = new GrantValidationResult(user.SubjectId, OidcConstants.AuthenticationMethods.Password);
        }

        return Task.FromResult(0);
    }
}

And add builder.AddResourceOwnerValidator<CustomResourceOwnerPasswordValidator>(); in the startup file.

Upvotes: 2

Related Questions