C#: Modify Claims via Middleware in ASP.NET
Tags: csharp, security, aspnet
Published: 2022-07-27 08:53:00

Most Web applications use OAuth 2.0 to authenticate/authorize uses these days. ASP.NET also has built-in support for OAuth 2.0 with Azure AD which we use quite often. 

However, in many projects, we found it difficult to include appropriate claims from the beginning because:

- Management team of AD is different
- There are strict policies that we cannot create test AD
- We don't know what scope to be created yet

In such care, we often neglect authorization settings in controller, but we shouldn't. To avoid this, I explain how to use middleware to modify passed claim so that we can tweak JWT.

There are several things to keep in our mind. (Great comment by @phlash909)

- If we modify JWT in this way, we cannot pass it to another service anymore.
- We can also offload this to another service, rather than directly modify claims to keep it clean.

## Web API

In this article, I start from normal Web API using template.

```shell
dotnet new webapi -n modifyjwt
cd modifyjwt
start .\modifyjwt.csproj
```

## Enable Authentication

Refer to [Quickstart: Protect a web API with the Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/web-api-quickstart?pivots=devlang-aspnet-core) to enable authentication against your Azure AD.

## Add require scope to existing controller

By default, there are not authorization settings in existing controller.

1\. Install Microsoft.Identity.Web nuget package.

2\. Add ``[RequiredScope]`` attribute to ``WeatherForecastController.cs``. I added ``ReadWeatherForecast`` as required scope.

```csharp
[HttpGet(Name = "GetWeatherForecast")]
[RequiredScope("ReadWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();
}
```

3\. Run the application and confirm you cannot access the controller.
![Failed to access with 404 unauthorized](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/68d7u5f0z0h46m1lclwr.png)


## Add middleware to modify JWT

1\. Add middleware class.

```csharp
using System.Security.Claims;

namespace modifyjwt;

public class MyAuthMiddleware
{
    private const string SCOPE_CLAIM_TYPE = "http://schemas.microsoft.com/identity/claims/scope";
    private readonly RequestDelegate _next;

    public MyAuthMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (context.User.Identity is not null
            && context.User.Identity.IsAuthenticated)
        {
            Claim scope = context.User.Claims.First(x => x.Type == SCOPE_CLAIM_TYPE);
            (context.User.Identity as ClaimsIdentity)?.RemoveClaim(scope);
            context.User.AddIdentity(new ClaimsIdentity(new List<Claim>()
            {
                new Claim(SCOPE_CLAIM_TYPE, "ReadWeatherForecast"),
            }));
        }

        // Call the next delegate/middleware in the pipeline.
        await _next(context);
    }
}

public static class MyAuthMiddlewareExtensions
{
    public static IApplicationBuilder UseMyAuthMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyAuthMiddleware>();
    }
}
```

2\. Use it in Program.cs between ``UseAuthentication`` and ``UseAuthorization``

```csharp
app.UseAuthentication();

app.UseMyAuthMiddleware();

app.UseAuthorization();
```

3\. Run the app to confirm it works.

![Success to get data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bry10mldmmabs33bh4ys.png)

## Add multiple scopes

If we need to add multiple scopes, we can add it by separate each scope with space like ``ReadWeatherForecast WriteWeatherForecast``.

## Summary

In production, we should get all necessary scopes from Azure AD, or at least fetch from data source by using user id.

- [ASP.NET Core Middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0)
- [Protected web API: Verify scopes and app roles](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-verification-scope-app-roles?tabs=aspnetcore)
- [Role-based access control for application developers](https://docs.microsoft.com/en-us/azure/active-directory/develop/custom-rbac-for-developers)