This tutorial will show you how to configure a client to use Resource Owner Password grant type. The work is based on IdentityServer4 Tutorial - Part 1: Basic Setup.

Define API Resources

The first thing is to define what API resources to protect. Modify ConfigureServices method in Startup:

public void ConfigureServices(IServiceCollection services)
{
    var builder = services.AddIdentityServer()
        .AddInMemoryClients(new Client[] { })
        .AddInMemoryApiResources(new[]
        {
            new ApiResource("api1")
        })
        .AddDeveloperSigningCredential();
}

Check out the API Resource document.

Define Clients

Now we can add clients. Modify ConfigureServices method in Startup:

public void ConfigureServices(IServiceCollection services)
{
    var builder = services.AddIdentityServer()
        .AddInMemoryClients(new[]
        {
            new Client
            {
                ClientId = "client_id1",
                RequireClientSecret = false,
                AllowedGrantTypes = { GrantType.ResourceOwnerPassword },
                AllowedScopes = { "api1" },
                AllowOfflineAccess = true
            }
        })
        .AddInMemoryApiResources(new[]
        {
            new ApiResource("api1")
        })
        .AddDeveloperSigningCredential();
}

The code defines a client without client secret. If you need a client secret, follow the Secrets document. AllowOfflineAccess is set to true which means a refresh token will be issued for every token request. By default, refresh tokens will be kept in memory. Later we will learn how to support other storages.

Add Resource Owner Password Validator

IdentityServer doesn’t know your resource owners’ credentials. You need to provide your own IResourceOwnerPasswordValidator implementation. The example below hard codes username and password. But you can inject your own user repository and do similar validation.

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
    public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        if (context.UserName == "username" && context.Password == "password")
        {
            context.Result = new GrantValidationResult(context.UserName, GrantType.ResourceOwnerPassword);
        }

        return Task.CompletedTask;
    }
}

Then we should tell IdentityServer to use our implementation.

public void ConfigureServices(IServiceCollection services)
{
    var builder = services.AddIdentityServer()
        .AddInMemoryClients(new[]
        {
            new Client
            {
                ClientId = "client_id1",
                RequireClientSecret = false,
                AllowedGrantTypes = { GrantType.ResourceOwnerPassword },
                AllowedScopes = { "api1" },
                AllowOfflineAccess = true
            }
        })
        .AddInMemoryApiResources(new[]
        {
            new ApiResource("api1")
        })
        .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
        .AddDeveloperSigningCredential();
}

Token Request

The token request endpoint is /connect/token. You can use the following cURL command to request a token. (PS: Change the port to match yours.) You can also import cURL to Postman. Or learn more about cURL and play around with it.

curl -X POST https://localhost:44360/connect/token -d "client_id=client_id1&grant_type=password&username=username&password=password"

When you get a JWT, go to https://jwt.io to find out the details embeded.

And here is the cURL script to use the refresh token. (PS: change the refresh token to a valid one.)

curl -X POST https://localhost:44360/connect/token -d "client_id=client_id1&grant_type=refresh_token&refresh_token=13c13411222c2bebf7ec3eef16b1f8bca1d91695c26a2a9ae15effd44c811281"

Custom Claims

You can add custom claims like this.

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
    public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        if (context.UserName == "username" && context.Password == "password")
        {
            var customClaims = new []
            {
                new Claim("custom_claim", "custom_value"), 
            };
            context.Result = new GrantValidationResult(context.UserName, GrantType.ResourceOwnerPassword, customClaims);
        }

        return Task.CompletedTask;
    }
}

But you need to tell IdentityServer to add them to your JWT.

public void ConfigureServices(IServiceCollection services)
{
    var builder = services.AddIdentityServer()
        .AddInMemoryClients(new[]
        {
            new Client
            {
                ClientId = "client_id1",
                RequireClientSecret = false,
                AllowedGrantTypes = { GrantType.ResourceOwnerPassword },
                AllowedScopes = { "api1" },
                AllowOfflineAccess = true
            }
        })
        .AddInMemoryApiResources(new[]
        {
            new ApiResource("api1", new [] { "custom_claim" })
        })
        .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
        .AddDeveloperSigningCredential();
}