I'm building a .NET core Web API that’s secured using AAD and which uses ADAL to call a downstream API using the on-behalf-of flow…. similar to this Azure Sample:
https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof
What are the best practices for the token cache that should be used in a scenario like this?
Is the default cache acceptable?
Should you ever not have a cache?
AuthenticationContext authContext = new AuthenticationContext(authority, null)
If you should build your own then is there a good reference implementation to use?
The right token cache for you to use is very subjective, and really depends on your architecture, performance requirements, and so on.
The default cache used by ADAL is an in-memory cache, which means that it may not persist across requests that your API receives. Furthermore, the default cache used by ADAL.NET is a static class, meaning that two different requests to your API might pick up the same cache object, which is often unexpected since those two requests are likely for different users. So using the default ADAL cache is generally not recommended - it really depends on how your web server works.
Instead, we recommend passing null
as the token cache if you can manage the performance hit, or preferably implementing your own token cache.
If you want to implement your own cache, it will save your app from having to make an outbound HTTP request to AAD (via ADAL) on every single incoming request. A sample ADAL cache implementation using the .NET entity framework is available here, and copied below as well:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Linq;
using System.Web;
namespace TodoListWebApp.DAL
{
public class PerWebUserCache
{
[Key]
public int EntryId { get; set; }
public string webUserUniqueId { get; set; }
public byte[] cacheBits { get; set; }
public DateTime LastWrite { get; set; }
}
public class EFADALTokenCache: TokenCache
{
private TodoListWebAppContext db = new TodoListWebAppContext();
string User;
PerWebUserCache Cache;
// constructor
public EFADALTokenCache(string user)
{
// associate the cache to the current user of the web app
User = user;
this.AfterAccess = AfterAccessNotification;
this.BeforeAccess = BeforeAccessNotification;
this.BeforeWrite = BeforeWriteNotification;
// look up the entry in the DB
Cache = db.PerUserCacheList.FirstOrDefault(c => c.webUserUniqueId == User);
// place the entry in memory
this.Deserialize((Cache == null) ? null : Cache.cacheBits);
}
// clean up the DB
public override void Clear()
{
base.Clear();
foreach (var cacheEntry in db.PerUserCacheList)
db.PerUserCacheList.Remove(cacheEntry);
db.SaveChanges();
}
// Notification raised before ADAL accesses the cache.
// This is your chance to update the in-memory copy from the DB, if the in-memory version is stale
void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
if (Cache == null)
{
// first time access
Cache = db.PerUserCacheList.FirstOrDefault(c => c.webUserUniqueId == User);
}
else
{ // retrieve last write from the DB
var status = from e in db.PerUserCacheList
where (e.webUserUniqueId == User)
select new
{
LastWrite = e.LastWrite
};
// if the in-memory copy is older than the persistent copy
if (status.First().LastWrite > Cache.LastWrite)
//// read from from storage, update in-memory copy
{
Cache = db.PerUserCacheList.FirstOrDefault(c => c.webUserUniqueId == User);
}
}
this.Deserialize((Cache == null) ? null : Cache.cacheBits);
}
// Notification raised after ADAL accessed the cache.
// If the HasStateChanged flag is set, ADAL changed the content of the cache
void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if state changed
if (this.HasStateChanged)
{
Cache = new PerWebUserCache
{
webUserUniqueId = User,
cacheBits = this.Serialize(),
LastWrite = DateTime.Now
};
//// update the DB and the lastwrite
db.Entry(Cache).State = Cache.EntryId == 0 ? EntityState.Added : EntityState.Modified;
db.SaveChanges();
this.HasStateChanged = false;
}
}
void BeforeWriteNotification(TokenCacheNotificationArgs args)
{
// if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With