RO EN

Claims Transformation in .NET

Claims Transformation in .NET
Doru Bulubasa
19 March 2026

How to build dynamic and multi-tenant permissions in ASP.NET Core

Series: Security by Design in .NET: From JWT to Certificate-based Authentication

In previous articles we discussed:

  • JWT

  • Refresh Tokens

  • Authorization (roles vs policies)

But a real problem arises in applications:

The JWT contains limited and static information.

What do you do when you need:

  • dynamic permissions from the DB

  • multi-tenant logic

  • roles that change without re-login

Here comes Claims Transformation.


🧠 What is Claims Transformation

Claims Transformation allows you to:

modify or add claims after authentication, on every request.

ASP.NET Core provides the interface:

IClaimsTransformation

It is executed after token validation.


⚙️ 1️⃣ Implementing IClaimsTransformation

Simple example:

public class CustomClaimsTransformation : IClaimsTransformation
{
    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        var identity = (ClaimsIdentity)principal.Identity;

        if (!identity.HasClaim(c => c.Type == "custom"))
        {
            identity.AddClaim(new Claim("custom", "true"));
        }

        return Task.FromResult(principal);
    }
}

Registration:

builder.Services.AddScoped<IClaimsTransformation, CustomClaimsTransformation>();


🧩 2️⃣ Enriching Claims from the database

The most useful scenario:

you add claims from the DB on every request.

Example:

public class DbClaimsTransformation : IClaimsTransformation
{
    private readonly IUserRepository _repo;

    public DbClaimsTransformation(IUserRepository repo)
    {
        _repo = repo;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        var identity = (ClaimsIdentity)principal.Identity;

        var userId = identity.FindFirst("sub")?.Value;

        var user = await _repo.GetUserWithPermissions(userId);

        foreach (var permission in user.Permissions)
        {
            identity.AddClaim(new Claim("permission", permission));
        }

        return principal;
    }
}


🏢 3️⃣ Multi-tenant logic

In real applications:

a user can belong to multiple companies.

The JWT contains:

{
  "sub": "user123"
}

But at runtime you need:

  • current CompanyId

  • Role in the company

  • Company-specific permissions

Claims Transformation can do this:

identity.AddClaim(new Claim("companyId", selectedCompanyId));
identity.AddClaim(new Claim("role", "Manager"));


🔄 4️⃣ Dynamic Permissions

Role-based is not enough in complex applications.

Instead:

permission = "invoice.read"
permission = "invoice.create"
permission = "invoice.delete"

Added as claims:

identity.AddClaim(new Claim("permission", "invoice.read"));

Then you use a policy:

options.AddPolicy("CanReadInvoice",
    policy => policy.RequireClaim("permission", "invoice.read"));


⚠️ Issues and pitfalls


❌ Execution on every request

Claims Transformation runs on every request.

If you query the DB:

→ performance impact


✅ Solutions

  • cache (MemoryCache / Redis)

  • claim versioning

  • data limitation


❌ Duplicate claims

If you don't check:

→ you will add the same claims multiple times


❌ Too complex logic

Don't turn ClaimsTransformation into a "mini service layer".


🧱 Recommended architecture

JWT contains:

sub (userId)
email

Claims Transformation adds:

permissions
companyId
roles

Authorization uses:

policies + handlers


🔥 Best Practices

✔ Minimal JWT (only identification)

✔ Claims enrichment from DB

✔ Cache for performance

✔ Dynamic permissions

✔ Multi-tenant aware

✔ Policy-based authorization


🎯 Conclusion

Claims Transformation is the key for:

✔ dynamic permissions

✔ multi-tenant applications

✔ flexible authorization logic

It is the difference between:

a simple role system
and
an enterprise permission system