Cached Claims when Using Windows Authentication in ASP.NET Core
In Loading Claims when Using Windows Authentication in ASP.NET Core
we examined an approach for injecting Claims into the ClaimsPrincipal
in order to enable policy usage – [Authorization(Policy = "SomePolicy")]
–
on controller actions.
One of the purposes of the IClaimsTransformation
implementation
is to provide an easier, and somewhat efficient, way to use
authorization policies. As such, we wouldn’t be wrong to perform
some expensive operations in the class implementing this interface.
For example, querying a database.
Having than happen on every request is a bit more than annoying while in development.
While we cannot avoid the calls to the claims transformer,
we can avoid the expensive calls by using a caching approach.
The title is misleading a bit at this point. We will be caching
the expensive calls and not the claims.
In the Windows claims example, we have
MagicPowersInfoProvider
as a way to provide information
to the claims transformer, MyClaimsLoader
, which in turn
determines whether a claim needs to be added to
the ClaimsIdentity
(in TransformAsync
).
MagicPowersInfoProvider
is registered as a singleton,
which makes is a good place to handle caching.
services.AddSingleton<Auth.MagicPowersInfoProvider>();
It only makes sense to cache when running under IIS Express.
Luckily, we don’t need to perform any complex detection of
IIS Express. We just need to modify the launchSettings.json
file to add an environment variable:
{
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"CacheClaims": "true"
}
},
...
Then MagicPowersInfoProvider
can make use of an injected IMemoryCache
when the "CacheClaims"
key is true, which would only be
when running the application from Visual Studio and under IIS Express.
public class MagicPowersInfoProvider
{
private const string CacheClaimsKey = "CacheClaims";
private const int ClaimCacheInSeconds = 5 * 60;
private readonly bool _cacheClaims;
private readonly IMemoryCache _memoryCache;
public MagicPowersInfoProvider(IConfiguration config, IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
_cacheClaims = config.GetValue<bool>(CacheClaimsKey);
}
public async Task<bool> CanHasPowerAsync(string userId)
{
if (!_cacheClaims)
{
return await ExpensiveHasPowerOperation(userId);
}
return await _memoryCache.GetOrCreateAsync<bool>(
$"power-{userId}",
async cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(ClaimCacheInSeconds);
bool hasPower = await ExpensiveHasPowerOperation(userId);
return hasPower;
});
}
private Task<bool> ExpensiveHasPowerOperation(string userId)
=> Task.FromResult(true);
}
For a full example, containing all the code, see this repo.