Search code examples
identityserver4role-base-authorizationrole-based-access-controlclientcredential

Role based access using identity server 4 and client credentials grant type


I'm just starting out with Identity Server 4.

I'm trying to protect an API using the Client Credentials grant type.

I have an API setup within IS4:

    public static IEnumerable<ApiResource> Apis =>
        new List<ApiResource> 
        {
            new ApiResource("myapi", "Test API")
            {
                ApiSecrets = { new Secret("secret".Sha256() )}
            }
        };

I also have the following client setup:

    public static IEnumerable<Client> Clients =>
        new List<Client>
        {
            new Client
            {
                ClientId = "testc",
                ClientName= "Test Client",
                AllowedGrantTypes = GrantTypes.ClientCredentials,
                ClientSecrets =
                {
                    new Secret("m2msecret".Sha256())
                },
                AllowedScopes = new List<string>
                {
                    "myapi"
                }
            },
        };

I have a controller within API that I'd like to protect:

[Authorize]
public class TestController : ControllerBase {} 

If I then create a token request as follows:

        var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
        {
            Address = disco.TokenEndpoint,
            ClientId = "testc",
            ClientSecret = "m2msecret",
            Scope = "myapi",
        });

This allows me to call the API and access the resource. Perfect!

But, I'd like to protect the controller with a role, e.g.

[Authorize(Roles = "admin")]
public class TestController : ControllerBase {} 

So my 2 questions are:

1: How do you set up role-based authorization using client credentials?

2: As client credentials doesn't link to a user, how can I keep an audit trail of record changes e.g. supplier X was updated by userId 5, etc.

Thanks


Solution

  • I see what you mean. But clients are not users, so you can't assign roles to a client.

    Let's take one step back. The resource is what you want to protect. In IdentityServer you can give the resource a logical name, logical because you can have multiple api's that are part of the same resource. And, what is less obvious, the resource has at least one scope. In the samples the resource has the name 'Api1' and an 'Api1' scope.

    A client can only access a resource if it has at least one allowed scope configured. Because it's the api name that is used to access the resource.

    To limit access based on scopes, you'll have to test the scopes that are included in the token. And this is the key, instead of using roles, you should use policies where you match on scope. In your startup:

    services.AddAuthorization(option =>
    {
        option.AddPolicy("testing", p => p.RequireScope("myapp.test"));
    });
    

    And in the controller:

    [Authorize("testing")]
    public class TestController : ControllerBase {} 
    

    Please note: parts that are not protected by policies like above will allow all clients that have at least one scope of the resource, regardless which scope it is.

    I suggest you split your resource in logical parts (scopes) and configure the clients to define which client can access which scope (part of the resource).

    The type of client doesn't matter. This works for the client credentials grant type, but also with interactive clients. Though in that case access can be retricted by user authorization.

    As for tracking users. For the interactive clients the user is available in the sub claim. With user authorization you can use roles.

    For the client credentials flow, this kind of information should be part of the request (data).

    For your design, you can use seperate contollers for the different types of clients and user authorization which can call the same services.