DEV Community

loading...
The 425 Show

Implement a distributed token cache for ASP.NET Core apps with Azure Cosmos DB

Christos Matskas
Program Manager in the Microsoft Identity Dev Advocacy team. Programmer, speaker and all around geek
Updated on ・3 min read

On a previous blog post we examined the steps necessary to add authentication to a Blazor Server app using the latest Microsoft.Identity.Web library. Everything worked as expected and we were able to authenticate against Azure AD, acquire an access token and retrieve data from Microsoft Graph. However, there is one small issue with the current implementation of our code. Our token cache is configured to run in memory. This is great for quickly testing the app but presents a few issues once we decide to deploy and run the app in production. The app can’t scale as the token cache is only available to the local instance and if, for whatever reason, the app restarts, all tokens in cache will be wiped out along with our cache.

Prerequisites

You need to have the following installed or available to you

  • An Azure AD tenant
  • .NET Core 3.1 or later
  • An Azure Cosmos DB instance or the local emulator
  • Visual Studio 2019 or Visual Studio Code

Implementing a distributed cache

Luckily for us, the Microsoft.Identity.Web library comes with built-in support for distributed caches. In fact, it only takes one line of code to configure it. This makes it extremely straightforward to create a robust solution right out of the box. The official documentation provides the following options for setting up a distributed cache:

  • SQL Server
  • Redis Cache
  • NCache

However, since all these cache providers implement the IDistributedCache interface, my assumption is that any provider that implements that same interface can be used with Microsoft.Identity.Web. And as luck will have it, there is already a provider for Cosmos DB (in preview) that implements this interface. Therefore, my assumption is that using this, our application should work straight out of the box without any code changes. Let’s do it!

First, we need to install the appropriate NuGet package. Use the option that works best for you (CLI, PowerShell, Package Manager etc.) to install the Microsoft.Extensions.Caching.Cosmos package

NuGet install

With the NuGet package installed, we need to edit the appsettings.json file and add the following parameters that will be used to instantiate our Cosmos DB cache which runs locally via the emulator:

"CosmosCacheContainer": "TokenCache",
"CosmosCacheDatabase": "BlazorCache",
"CosmosConnectionString": “AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",

NOTE: note that the AccountKey on the connection string is a well know key that is common for anyone using the emulator, hence the reason I didn’t omit it here. #security

The one last thing we need to do is update our middleware code in Startup.cs to register the Cosmos DB cache and make it available to our authentication middleware. Paste the following code:

services.AddCosmosCache((CosmosCacheOptions cacheOptions) =>
{
    cacheOptions.ContainerName = Configuration["CosmosCacheContainer"];
    cacheOptions.DatabaseName = Configuration["CosmosCacheDatabase"];
    cacheOptions.ClientBuilder = new CosmosClientBuilder(Configuration["CosmosConnectionString"]);
    cacheOptions.CreateIfNotExists = true;
});

services.AddMicrosoftWebAppAuthentication(Configuration)
    .AddMicrosoftWebAppCallsWebApi(Configuration, new string[] { "User.Read", "Mail.Read" })
.AddDistributedTokenCaches();

Running the code again, we can see that upon authentication, our Cosmos DB cache gets populated with our token information that can be used later by our app to retrieve the data from MS Graph:

CosmosDB cache

Video recording

There is also a video recording where we (Christos and JP) show you how we put this all together live on Twitch!

Twitch stream

The Identity Dev Advocates team streams on Tuesdays at 7am PT and Thursdays at 8am PT on Twitch. We cover all things identity and we like to bring guests and customers on the air to learn how they use our tools and platform to build their solutions.

If you want to get involved or have any questions make sure to reach out to Christos Matskas or JP

Summary

As you can see, the latest Microsoft.Identity.Web makes it very easy to work with distributed caches in a ‘plug-n-play’ mode. And, as long as, a cache provider implements the IDistributedCache interface, you can use any custom cache with your ASP.NET Core app.

Discussion (8)

Collapse
fenushah profile image
Fenil Shah

We started using this package: Microsoft.Extensions.Caching.Cosmos, but was just confused with one thing that are you aware of when Microsoft is going to release this NuGet package?

Collapse
christosmatskas profile image
Christos Matskas Author

Thanks @Fenil Shah... I reached out to the team to find out. I'll update this thread once I know

Collapse
christosmatskas profile image
Christos Matskas Author

We are aiming to go GA within the next 6 months. However, the current preview bit is in full support and the team is monitoring GitHub for any issues so you should be able to go ahead and use it. The good news is that we don't anticipate any code changes between the Preview and GA bits.

Thread Thread
fenushah profile image
Fenil Shah

Thank you so much Christos for your time and clear view of the package :)

Collapse
sameerkumar profile image
Sameer

Very nice. This vs RedisCache? Recommendations? Thanks!

Collapse
christosmatskas profile image
Christos Matskas Author

It's totally up to you. Cosmos DB has very low latency but comes at a slightly higher cost. It depends on what you prefer to work with :)

Collapse
fenushah profile image
Fenil Shah • Edited

I followed these above things and the token is getting saved perfectly. Now if we need to consume the same token for graph API access then how we will get/fetch the token?

Collapse
christosmatskas profile image
Christos Matskas Author

Your MSAL client is able to get your token from the cache. When you do client.acquireTokenForUserSilent() it will look into the cache first... so it's seamless to you if you implement it correctly :)