Recommended ADAL Token Cache for Web API?
I am creating a basic .NET web API that is secured using AAD and that uses ADAL to call a downstream API that uses a stream on behalf of ... similar to this Azure sample:
https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof
What are the best techniques for a token cache that should be used in such a scenario?
-
Is the default cache valid?
-
If you don't have a cache?
AuthenticationContext authContext = new AuthenticationContext (authority, null)
-
If you have to create your own then is there a good reference implementation to use?
source to share
The correct token cache is very subjective for you and depends on your architecture, performance requirements, etc.
The default cache used by ADAL is an in-memory cache, which means it cannot persist across requests received by your API. Also, the default cache used by ADAL.NET is a static class, which means that two different requests to your API can get the same cache object, which is often unexpected, since these two requests can be for different users. Therefore, using the default ADAL cache is generally not recommended - it really depends on how your web server is running.
Instead, we recommend passing null
as a token cache if you can manage a performance hit or would prefer to use your own token cache.
If you want to implement your own cache, it will save your application from having to send an outgoing AAD HTTP request (via ADAL) for every incoming request. An example of ADAL cache implementation using the .NET entity framework is available here and also copied below:
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
}
}
}
source to share