2
votes

I have a basic IdentityServer4 token server, an Api, and a test client application setup using client_credentials based on the identityserver4 docs tutorial.

We have a pre-built client application that users log into with their existing credentials which is not tied into IdentityServer4. The client application will call the Api using the client_credentials workflow because I do not want to create multiple users for every client application that may need to access the Api.

Using the above setup with IdentityServer4 I have this working correctly with the client_Credentials workflow. The issue I am facing is although I do not need the individual user to authenticate themselves I still want to know who they are, by way of a user_id. I can simply add &user_id=9999 to the token request but I could not find a way to retrieve this information from the tokenserver at the time the request is made. After some research I came across the IExtensionGrantValidator which would allow me to add a cstom grant type and intercept the request and do some custom processing. The problem is even though it looks as if I set it up correctly I am still getting the invalid_grant error.

Here is the code:

public class CustomGrantValidator : IExtensionGrantValidator
{
    public string GrantType => "custom_credentials";

    public Task ValidateAsync(ExtensionGrantValidationContext context)
    {
        return Task.FromResult(context.Result);
    }
}

In the new Client block:

 AllowedGrantTypes =
 {
     GrantType.ClientCredentials,
     "custom_credentials"
 },

In Startup

.AddExtensionGrantValidator<CustomGrantValidator>();

I am new to IdentityServer4 and .net Core so I am sure I am doing something wrong or not understanding a fundamental mechanic here.

2
how did you resolve it?Sana
I believe I found a way to add the claims without going through the GrantValidator, but I no longer have the code and cannot recall what I ended up doing. Ultimately, I gave up on IdentityServer4. Due to compatibility issues across frameworks and it was overkill for what I was trying to achieve anyway. I decided to use a .NET JWT implementation which made much more sense for our use-case.Dmcalister

2 Answers

3
votes

In order to get a IExtensionGrantValidator with a successful answer, you have to implement the interface IProfileService. This interface has a method called IsActiveAsync. If you don't implement this method, the answer of ValidateAsync will always be false. Here I'll show you an implementation example:

public class IdentityProfileService : IProfileService
    {

        //This method comes second
        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            //IsActiveAsync turns out to be true 
            //Here you add the claims that you want in the access token
            var claims = new List<Claim>();

            claims.Add(new Claim("ThisIsNotAGoodClaim", "MyCrapClaim"));

            context.IssuedClaims = claims;
        }

        //This method comes first
        public async Task IsActiveAsync(IsActiveContext context)
        {           
            bool isActive = false;
            /*
                Implement some code to determine that the user is actually active 
                and set isActive to true
            */
            context.IsActive = isActive;
        }
    }

Then, you have to add this implementation in your startup page.

public void ConfigureServices(IServiceCollection services)
{

    // Some other code
services.AddIdentityServer()
                    .AddDeveloperSigningCredential()
                    .AddAspNetIdentity<Users>()
                    .AddInMemoryApiResources(config.GetApiResources())
                    .AddExtensionGrantValidator<CustomGrantValidator>()
                    .AddProfileService<IdentityProfileService>();
    // More code
}

Your implementation can (and I think that will) be more complex, but I hope this gives you and good starting point.

1
votes

I found this because I'm having issues with the same error, but I am sure that your validator is useless because it doesn't do anything. You need to set the Result on the context like this:

        var claims = new List<Claim>();

        claims.Add(new Claim("sub", userToken));
        claims.Add(new Claim("role", "admin"));

        context.Result = new GrantValidationResult(userToken, "delegation", claims: claims);

Every example I've seen sets the result by adding this value.