Card image cap

Implementing ClientCredentials Grant Flow using IdentityServer4

ASP.NET Core IdentityServer4  • Posted 5 months ago

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 ClientCredentials 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 ClientCredentials 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 ClientCredentials 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.

data/Admin/2020/6/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 the 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

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 ClientCredentials grant flow which can help control access to API resources for limited clients.

Enjoying my posts?
You can now show me your support! 😊

What is the difference between Run() and Use() methods in IApplicationBuilder?

* Use() method: Used to create a simple middleware which can be "chained" to other functions over the pipeline. Takes two arguments: RequestDelegate ...


What is the difference between Response.Redirect() and Server.Transfer() ?

* Response.Redirect() redirects browser to another page, history is updated, trip back to client where browser loads the new page. * Server.Transfer( ...


How do you handle errors Globally in ASP.NET Core?

We can make use of the built-in UseExceptionHandler() middleware for catching Global Errors in ASP.NET Core. ``` app.UseExceptionHandler(err => ...


How do you design a strongly-typed class for a configuration?

To create a strongly-typed class for binding to a configuration section: * The property names and their types match the key names and their value t ...


How can you bind a configuration section to an object?

A Configuration section can be bound to a strictly-typed class object in two ways: * use Configuration.Bind() by passing the configuration section to ...


identityserver4 client credentials grant type identityserver4 client credentials grant client credentials grant c# identity server 4 client credentials example identityserver4 client credentials flow asp.net core client credentials asp.net core client credentials flow asp.net core webapi secured using oauth2 client credentials

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept