Search code examples
c#asp.netasp.net-coreidentityserver4oidc-client-js

Trying to add IdentityServer4 as identity authority for a Javascript application with custom user store


I've followed the IdentityServer4 documentation to create a basic local implementation using the default test configuration (with in-memory clients and users). At this point IS4 is initialized as follows:

services.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddInMemoryIdentityResources(Config.GetIdentityResources())
    .AddInMemoryApiResources(Config.GetApiResources())
    .AddInMemoryClients(Config.GetClients())
    .AddTestUsers(Config.GetUsers());

I've then followed the instructions for creating a simple Javascript application using the oidc-connect library. This works fine so now I have a Javascript application which lets a user sign in via my IS4 instance. This JS application is represented within IS4 as a client using implicit flow as per the instructions.

I now want to integrate my IS4 instance with my real users store. Having read a few articles it would seem I need to provide implementations for IResourceOwnerPasswordValidator and IProfileService as per this SO post and various others.

I've done this, for now just using a dummy user repository which works with a dummy set of in-memory users rather than a real external store. My IS4 initialization now looks like this:

services.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddInMemoryIdentityResources(Config.GetIdentityResources())
    .AddInMemoryApiResources(Config.GetApiResources())
    .AddInMemoryClients(Config.GetClients());

builder.Services.AddTransient<IProfileService, CustomProfileService>();
builder.Services.AddTransient<IResourceOwnerPasswordValidator, CustomResourceOwnerPasswordValidator>();

Now when I go back and test my Javascript application this is where it gets weird. When trying to sign in I'm taken to my IS4 login screen. I enter the credentials I expect to work but Invalid username or password is returned. Debugging I can see that the code never enters my ValidateAsync method in CustomResourceOwnerPasswordValidator. However, by chance I've found that if I enter either bob or alice as username and password (so, for example, entering bob as username and password) then I'm authenticated and can return back to my application. This doesn't make any sense. I'm sure it's no coincidence that those are the usernames which are the two default users used by the IdentityServer4 sample in-memory users, but you can see above I'm no longer using in-memory users. In fact, this is still true even when I comment the GetUsers method out of Config.cs.

I can't explain this, but in any case what I want is to be able to use my user store. So I read a bit further and have seen suggestions that I can only use IResourceOwnerPasswordValidator with clients that have GrantTypes.ResourceOwnerPassword. However, when I change my Javascript app's client in IS4 to use this grant type, when I try to log in I see an unauthorized_client error.

So I'm stuck. I had thought that since I had my setup working with in-memory users then it wouldn't be much work to use a real user store, but clearly there's something I'm missing. Your guidance would be much appreciated.


Solution

  • You don't need to inject your ProfileService as a dependency in your services.

    You need something like:

    services.AddIdentityServer()
                .AddProfileService<CustomProfileService>()
                //..... more code
    

    And furthermore - your CustomProfileService doesn't need to implement IProfileService (the method accepts a generic).

    The real "magic" happens in the AccountController in :

    /// <summary>
        /// Handle postback from username/password login
        /// </summary>
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Login(LoginInputModel model)
        {
            // logic
        }
    

    There, in the LoginInputModel you have the username, password etc. That you need to validate, so at the end there is no real need of the IProfileService if you are writing your custom user management.