Post

Current user implementation in ASP.NET Core!

In this article, I’ll go through a couple of implementations on how to extract and provide user information in ASP.NET Core web applications. We apply authentication and authorization to protect our endpoints, and usually, we utilize the user information throughout our services. That may be for logging purposes, auditing or other reasons. Fetching this data over and over again is neither performant nor convenient. Since this data doesn’t change per request, ideally, we’d like to extract it once and provide it in an immutable form to all our services.

Let’s start by defining an interface that will expose user information.

1
2
3
4
5
public interface ICurrentUser
{
    public string? UserId { get; }
    public string? Username { get; }
}

Option 1

The most common approach I see in various solutions is to utilize the IHttpContextAccessor to extract the necessary data from the current request. This works, but it’s not ideal. It relies on AsyncLocal which can have a negative performance impact and creates a dependency on the “ambient state”. I try to avoid this interface as much as possible and use it only as a last resort. The implementation would be as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CurrentUser : ICurrentUser
{
    public string? UserId { get; }
    public string? Username { get; }

    public CurrentUser(IHttpContextAccessor httpContextAccessor)
    {
        var claimsPrincipal = httpContextAccessor.HttpContext?.User;

        if (claimsPrincipal is null) return;

        UserId = claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier);
        UserId = claimsPrincipal.FindFirstValue(ClaimTypes.Name);
    }
}

Once we do the following registrations in DI, we may consume the ICurrentUser from all our services.

1
2
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<ICurrentUser, CurrentUser>();

Option 2

A better approach would be to not utilize IHttpContextAccessor, instead, use a custom middleware to extract the information early in the request. Since our ICurrentUser is fully immutable, we’ll create an additional contract.

1
2
3
4
5
public interface ICurrentUserInitializer
{
    public string? UserId { get; set; }
    public string? Username { get; set; }
}
1
2
3
4
5
public class CurrentUser : ICurrentUser, ICurrentUserInitializer
{
    public string? UserId { get; set; }
    public string? Username { get; set; }
}

Now, we go ahead and implement the middleware. The ICurrentUserInitializer is not mandatory to have. It’s just convenient for testing where we may inject a faker with pre-populated test user data. That’s the reason, we’re setting the user data only if they’re empty currentUser.UserId ??.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static class CurrentUserExtensions
{
    public static IApplicationBuilder UseCurrentUser(this IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            var user = context.User;
            var currentUser = context.RequestServices.GetRequiredService<ICurrentUserInitializer>();

            currentUser.UserId ??= user.FindFirstValue(ClaimTypes.NameIdentifier);
            currentUser.Username ??= user.FindFirstValue(ClaimTypes.Name);

            await next();
        });

        return app;
    }
}

Finally, we need to wire up everything as follows.

1
2
3
4
5
6
7
8
9
10
11
...
builder.Services.AddScoped<CurrentUser>();
builder.Services.AddScoped<ICurrentUserInitializer, CurrentUser>(x => x.GetRequiredService<CurrentUser>());
builder.Services.AddScoped<ICurrentUser, CurrentUser>(x => x.GetRequiredService<CurrentUser>());
var app = builder.Build();

...
app.UseAuthorization();
app.MapControllers();
app.UseCurrentUser();
app.Run();

I hope you found the article useful and happy coding!

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.