How to – Client Credentials with IdentityServer4

In this article, let's look at how to configure and implement Client Credentials grant with IdentityServer4 and validate with example.

In a previous article, we have looked in detail about what a SecureTokenServer is and how to configure our own TokenServer for securing applications using IdentityServer4, which is an Open Source library to setup and implement Token functionalities and Session management for applications following the OAuth standards.

We have also looked at the fundamentals involving a TokenServer such as Clients, APIs and briefly about the types of Authentication Flows defined by the OAuth and OpenID standards.

In this article, let’s look at Implementing ClientCredentials auth flow, which is one of the basic auth flows used for securing and granting access to API resources to clients which possess an access_token issued by the configured TokenServer safeguarding the API.

What is a ClientCredentials Flow?

ClientCredentials grant type is one of the Flows prescribed by the OAuth standards which is defined as follows:

The Client Credentials grant is used when applications request an access token to access their own resources, not on behalf of a user.

It is a flow used by a client by passing its clientId and a clientSecret which are provided by the token server for registered clients. The token server recognizes a registered client by the values of clientId and clientSecret passed in its request and upon validation the server provides the requesting client with an access_token which provides a limited access for the client against a resource.

This flow is particularly useful for safeguarding APIs and controlling its access for only a few select clients which are registered with the token server for access.

In this way, the token server can both protect the API resources from being publicly accessible and at the same time it can create fine grained access control for clients over the API resources, so that it can dictate which client can access what.

Since this flow is all about access control over resources dealing with a Client and a ApiResource, any User related information is out of scope for this flow.

Why to use Client Credentials Flow?

  • For scenarios involving machine-to-machine communications such as service calls, jobs and such where User interactions don’t exist
  • For securing APIs against unwanted public access making it available for only a select few
  • For fine-grained access control over the resource access and defining access limits for clients

How Client Credentials Flow works?

The flow works in the below steps:

  1. The client / API which seeks access makes a call to the token server for access_token with its clientId and clientSecret
  2. The token server validates the clientId and clientSecret, along with other parameters such as scopes and the GrantType for any matching client
  3. If there is a matching client, the token server returns an access_token with optional details about the scopes and expiryTime
  4. The client / API passes the access_token it has just obtained in an Authorization header when making a request for the resource it seeks.
  5. The API which is guarded by the TokenServer picks up the access_token passed in the Header and validates it against its own knowledge of the registered clients and from the metadata obtained from the token server
  6. If validated, the API allows the request to access its resource, else returns a 401 UnAuthorized response to the requesting client.

The token request format for a typical ClientCredentials grant flow would look like this:

POST /connect/token HTTP/1.1
Host: token_server_uri
Content-Type: application/x-www-form-urlencoded

client_id=<client_name>&client_secret=<client_secret>&scope=<client_registered_scopes>&grant_type=client_credentials

The request is a POST call to the endpoint /connect/token (or the respective token endpoint of the token providers) of the IdentityServer4 Server. The mandatory parameters which are to be passed in a form content are:

  • client_id
  • client_secret
  • scope – the scopes which the client is allowed to access for. In this case “api1” is an ApiResource which the client seeks to access
  • grant_type=client_credentials

Note that these values being passed in the /connect/token “must” match the client configuration inside the TokenServer, otherwise the client is denied of issuing an access_token.

Implementing Client Credentials Grant Flow in IdentityServer4 –

To implement a ClientCredentials grant flow, we are required to create a client which is configured to use “Client_Credentials” for access in the TokenServer. We would also create an “ApiResource” which represents an API resource this “client” seeks to access.

The respective configurations of Client and ApiResource inside the Config we used in our previous example, look like below:

public static IEnumerable<ApiResource> Apis =>
    new List<ApiResource>
    {
        new ApiResource("api1", "My API")
    };

public static IEnumerable<Client> Clients =>
    new List<Client>
    {
        new Client
        {
            ClientId = "client",
            // no interactive user, 
            // use the clientid/secret for authentication
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            // secret for authentication
            ClientSecrets =
            {
                new Secret("secret".Sha256())
            },

            // scopes that client has access to
            AllowedScopes = { "api1" }
        }
};

And we feed them to the IdentityServer4 service in the Startup class, which registers the “client” as a Client which is expected to request access only for the ApiResource “api1” by means of “ClientCredentials” grant only.

var builder = services.AddIdentityServer()
    .AddInMemoryApiResources(Config.Apis)
    .AddInMemoryClients(Config.Clients)
    .AddInMemoryIdentityResources(Config.Ids);

To verify that the setup is working, check that the IdentityServer4 Discovery endpoint must show “api1” as a scope meaning that the scope is now available for access.

wp-content/uploads/2022/05/openid-spec.png

Next, we begin by making a POST call to the IdentityServer4 token endpoint with the details of the “client”.

POST /connect/token HTTP/1.1
Host: localhost:5001
Content-Type: application/x-www-form-urlencoded

client_id=client&client_secret=secret&scope=api1&grant_type=client_credentials

Observe that for the client_secret, we’re using the string “secret” directly and NOT the Sha256 hashed value which is passed inside the Secret() instance inside the Client – something which needs to be taken note of.

When we make this POST call, the response would be something like this:

{
    "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InN2cjJrVjdUWUNDenRSclpScjNJQ2ciLCJ0eXAiOiJhdCtqd3QifQ.eyJuYmYiOjE1OTI3NTI2NzQsImV4cCI6MTU5Mjc1NjI3NCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6N
    TAwMSIsImF1ZCI6ImFwaTEiLCJjbGllbnRfaWQiOiJjbGllbnQiLCJzY29wZSI6WyJhcGkxIl19.QbYgs7Wo5EBdmvddkhwKkr0g0wBYPm2GTJC4uIcM449CVFZysK5RcoJLzSwF-CycgNxeMZnZUIoac8GeQmJeQ
    k6T6zGodZgOSLKfH6C9sTyEUdtk7cWqQPpy65WDJ3K-zkgYWg2J2XpcDzZ3Av_T95NoQ9q-5DJD8qp5sxPOkT
    FoeX6vTpVcIvwdgBB4zbD61FPm_8ycf_2wWE-pgiYSPHWzZG7KKTWRL9AArNwW6aq
    M_GLjIF9ALgakt2qgebolTUN6kaoJWd4Pu_CANAnsP2TY8a1lFZV
    O4SQ_nH2GUl1oyZwsBqd7DlgZR4SoZx0Xyhs_Xp0gMYR0DjCHqAITKg",
    "expires_in": 3600,
    "token_type": "Bearer",
    "scope": "api1"
}

Along with the access_token, we get:

  • the kind of token it is – in this case its a JWT token which follows Bearer AuthenticationScheme
  • the expires_in which denotes the milliseconds before which the token is deemed to be expired
  • the scopes which the token can be used for access – in this case its the “api1” which the client is configured against

When we examine the contents of this token using a validator such as one available in jwt.io, we can see that it comes with the following contents:

{
  "nbf": 1592752674,
  "exp": 1592756274,
  "iss": "https://localhost:5001",
  "aud": "api1",
  "client_id": "client",
  "scope": [
    "api1"
  ]
}

The iss indicates the Issuer of the token, which in this case is the IdentityServer4 local instance. The aud indicates the Audience for which the token was issued in favor of – in this case it is the ApiResource “api1”. exp is the expiry in terms of UTC and client_id refers to the client which has requested for token. Seems pretty straight forward right?

Validating Token inside the API Resource –

To use this token against an API for which it is issued for access, we need to configure the API with capability to examine and validate this token. In our API project, add the JWT Authentication middleware and configure the TokenServer values in it.

To add the Authentication capabilities we begin by installing the JwtBearer package.

> dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

The Authentication service looks like below:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://localhost:5001";
        options.RequireHttpsMetadata = false;
        options.Audience = "api1";
    });

Observe that we’ve provided the hosted uri of the IdentityServer4 token server as the “Authority” and the scope “api1” as the Audience. Finally we decorate the API resource which is to be secured using the TokenServer with the [Authorize] attribute as below:

[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    // All Endpoints under this controller
    // are accessible only with a valid Token
    // issued by the TokenServer
}   

To test this, we shall make a GET call to the API controller along with the token we obtained in the previous step.

GET /weatherforecast HTTP/1.1
Host: localhost:5003
Authorization: Bearer access_token_comes_here

Buy Me A Coffee

Found this article helpful? Please consider supporting!

On receiving this API request, the Authentication middleware kicks in which examines the token for the valid Audience and attributes matching that of a token produced by the TokenServer. Since this is valid, the token is allowed for a brief access until the token expires.

In this way, we implement a simple Client Credentials grant flow which can help control access to API resources for limited clients.


Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

Your email address will not be published. Required fields are marked *