I have a rest service on Azure AD that calls other rest services. They use AzureAd authentication and AuthContext with a custom TokenCache that stores each users tokens in an InMemory EF Core database. Sometimes I'd get exceptions in AfterAccessNotification when I'd call SaveChanges().
var cache = context.TokenCacheEntries.Find(userObjectId) ?? context.TokenCacheEntries
.Add(new TokenCacheEntry { userObjId = userObjectId }).Entity;
cache.LastWrite = DateTime.Now;
cache.cacheBits = Serialize();
// update the DB and the lastwrite
context.SaveChanges();
HasStateChanged = false;
A possible cause of this is I have to initialize this context in the Singleton scope because I use it via AuthContext in my JwtBearerEvents.OnTokenValidated method.
I solved the problem with a lock:
private static object saveLock = new object();
/// <remarks>
/// Notification raised after ADAL accessed the cache.
/// If the HasStateChanged flag is set, ADAL changed the content of the cache
/// </remarks>>
private void AfterAccessNotification(TokenCacheNotificationArgs args)
{
if (HasStateChanged)
{
// SaveChanges() threw an exception that a key already existed once so lets hope this lock fixes that.
lock (saveLock)
{
// retrieve last write from the DB
var cache = context.TokenCacheEntries.Find(userObjectId) ?? context.TokenCacheEntries
.Add(new TokenCacheEntry { userObjId = userObjectId }).Entity;
cache.LastWrite = DateTime.Now;
cache.cacheBits = Serialize();
// update the DB and the lastwrite
context.SaveChanges();
HasStateChanged = false;
}
}
}
Is there a better way?
lockworks pretty well here, assuming you don't have multiple users with a need to call to it at once. You can also catch the exception being thrown and recheck to see if there's a valid token in the cache. \$\endgroup\$