whatever
whatever

Reputation: 3687

ASP.NET Core: enrich AAD token claims with custom ones?

In an ASP.NET Core application, I need to enrich the AAD token claims with custom ones coming from my application database, and I've seen code implementing the IClaimsTransformation interface to achieve this.

But it is totally bad for performance as the TransformAsync method is getting called every time the user needs to be authorized (meaning every time a page that requires authorization is navigated) and the ClaimsPrincipal passed to the method is always the original one meaning that claims need to be added each and every time.

I expected that claims would need to be added only once and that they would be kept/persisted for the current session. Is there any alternative to the IClaimsTransformation interface?

Upvotes: 0

Views: 266

Answers (2)

whatever
whatever

Reputation: 3687

Implementing the OpenIdConnectEvents.OnTokenValidated handler seems to achieve the same result with the added advantage that your custom claims are getting persisted across requests and thus claim enrichment needs to take place only once per user authentication and not once per user authorization (per page requiring authorization).

What risk is there in implementing it this way?

builder.Services.Configure<MicrosoftIdentityOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.Events = new OpenIdConnectEvents
    {
        OnTokenValidated = tokenContext =>
        {
            // getting your DB connection string
            var connStr = builder.Configuration.GetConnectionString("MyDatabaseConnStr");
    
            // your claim enrichment code...

            return Task.CompletedTask;
        },
    };
});

Upvotes: 1

Jason Pan
Jason Pan

Reputation: 22114

IClaimsTransformation is officially recommended, so as to ensure that the extended key-value can be obtained when authenticating.

It's okay to do it in other ways, such as middleware, but the order of the middleware is critical, and it's likely to trigger unexpected effects.

I think the key to your performance concern is that you need to read the database every time users authenticate, so we can use caching to avoid having to read the database every time.

Sample code like below

public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
    var identity = (ClaimsIdentity)principal.Identity;
    var userId = identity.FindFirst(ClaimTypes.NameIdentifier)?.Value;

    if (userId != null && !identity.HasClaim(c => c.Type == "CustomClaimType"))
    {
        var claims = await GetClaimsFromCacheOrDatabaseAsync(userId);
        identity.AddClaims(claims);
    }

    return principal;
}

private async Task<IEnumerable<Claim>> GetClaimsFromCacheOrDatabaseAsync(string userId)
{
    if (!_cache.TryGetValue(userId, out List<Claim> claims))
    {
        claims = (await _userClaimsService.GetClaimsAsync(userId)).ToList();
        _cache.Set(userId, claims, TimeSpan.FromMinutes(30)); // set expire time
    }

    return claims;
}

Upvotes: 1

Related Questions