aspnet-core | api-management | identityserver

ASP.NET Core - OAuth 2.0 Client Access Token Management

OAuth 2.0 client access token management in an ASP.NET Core application

Abhith Rajan
Abhith RajanJanuary 09, 2022 · 4 min read · Last Updated:

In a microservice world, the machine (microservice A) to machine (microservice B) communications can be secured using an OAuth 2.0 compatible token service, IdentityServer in our case. i.e To successfully call microservice B, microservice A needs to get an access token first via client credentials grant type from the IdentityServer with the right scope for the operation.

  • microservice A calls IdentityServer to get an access token.
  • microservice A then calls microservice B with the access token in the authorization header (bearer token).
  • microservice B validates the access token, responds to microservice A with a valid response.

The above steps are fine for a single call, but in case of multiple calls from A to B, A doesn’t need to get an IdentityServer token every time since each token has its validity, so basically, A can reuse the token until it expires.

For the token reusing, you don’t need to implement the caching and refreshing token logic anywhere since some good people who are maintaining IdentityModel.AspNetCore already done that. All we need to do is set up it, and consume it.

The library will take care of the following,

  • caching abstraction for access tokens
  • automatic renewal of expired access tokens
  • token lifetime automation for HttpClient

let’s jump into the setup. Following are our assumptions,

  • microservice B has endpoints,
    • POST /orders and it requires IdentityServer token with orders.write scope to create new orders,
    • and to fetch the orders GET /orders endpoint, the scope for that one is orders.read.
  • microservice A has valid client credentials configured on the IdentityServer with the above scopes added to the allowed scopes. i.e microservice A is allowed to call the microservice B.

So let’s configure microservice A,

  • Install IdentityModel.AspNetCore package

In the Startup.cs > ConfigureServices

// Setup token management service
services.AddAccessTokenManagement(options =>
{
  options.Client.Clients.Add(AppConsts.StsClientName, new ClientCredentialsTokenRequest
  {
    Address = $"{Configuration.GetValue<string>("Authentication:Sts:BaseUrl")}/connect/token", // Token service (IdentityServer) base URL
    ClientId = Configuration.GetValue<string>("Authentication:Sts:ClientId"),
    ClientSecret = Configuration.GetValue<string>("Authentication:Sts:ClientSecret"),
    Scope = $"{AppConsts.OrdersWriteScope} {AppConsts.OrdersReadScope}" // Any number of scopes, space separated
  });
});

// Adds a named HTTP client for the factory that automatically sends the client access token
services.AddClientAccessTokenClient(AppConsts.StsClientName, AppConsts.StsClientName, client => client.BaseAddress = new Uri(Configuration.GetValue<string>("ApiBaseUrl"))); // microservice B base URL

Here we setup the token management service and a named HttpClient that uses the same. Also you can observe, we moved the static string params to a AppConsts.cs file,

public class AppConsts
{
    public const string OrdersWriteScope = "orders.write";
    public static string OrdersReadScope = "orders.read";
    public static string StsClientName = "microservice_a_api_client";
}

And the configurations like client credentials, token service endpoint, and rest are kept in the appsettings.json or somewhere more private like Azure App Configuration, keyvault, and loaded to the aspnetcore configurations.

  "ApiBaseUrl": "https://microservice-b.com",
  "Authentication": {
    "Sts": {
      "BaseUrl": "https://my-identityserver.com",
      "ClientId": "microservice_a_api",
      "ClientSecret": ""
    }
  },

Configuration is done, now let’s consume the HttpClient. Here I’m using Flurl to make the HTTP request, if you prefer direct HttpClient usage, you can do that way. You don’t need to do anything special to get the access token since it is already taken care of.

using Flurl.Http;

public class GetOrdersQueryHandler : IRequestHandler<SomeInput, SomeOutput>
{
    private readonly IFlurlClient _flurlClient;

    public GetOrdersQueryHandler(IHttpClientFactory httpClientFactory)
    {
        var httpClient = httpClientFactory.CreateClient(AppConsts.StsClientName);
        _flurlClient = new FlurlClient(httpClient);

    }

    public async Task<SomeOutput> Handle(SomeInput request, CancellationToken cancellationToken)
    {
        return await _flurlClient
                .Request($"/orders")
                .GetJsonAsync<SomeOutput>(cancellationToken);
    }
}

As you can see, we use the named httpClient, and that client is already configured to have the access token with the scopes. That’s it. You can use the same way to call the create order endpoint since the client has an access token with both scopes.

Additional Resources

This page is open source. Noticed a typo? Or something unclear?
Improve this page on GitHub


Abhith Rajan

Written byAbhith Rajan
Abhith Rajan is a software engineer by day and a full-stack developer by night. He's coding for almost a decade now. He codes 🧑‍💻, write ✍️, learn 📖 and advocate 👍.
Connect

Webmentions

Is this page helpful?

Related ArticlesView All

Related VideosView All

Why I DON'T use MediatR in ASP.NET Core

Don't Use AutoMapper in C#! Do THIS Instead!

Do you need IdentityServer?

Related StoriesView All

Related Tools & ServicesView All

jwt.io

JWT.IO

JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).
flurl.dev

Flurl

Flurl is a modern, fluent, asynchronous, testable, portable, buzzword-laden URL builder and HTTP client library for .NET.