Developments in Digital
Developments in Digital

Managing identity in ASP.NET Core

When performing authentication (identity) in ASP.NET Core, the default approach is to reference the ClaimsPrincipal exposed from the User property within a controller:

[HttpGet]
[Route("example")]
public IActionResult Get()
{
  // access this.User here, including:
  // this.User.Identity.IsAuthenticated
  // this.User.Identity.Name
  // this.User.Claims
  return this.Ok();
}

However, there are a few issues with this approach:

1) It is difficult to test functionality given different users. Although I do not recommend testing controllers directly, you still need to the ability to stub/mock out the current identity when integration testing with Microsoft.AspNetCore.TestHost. 2) You often want to test for particular claims of the current identity and string claim values may need to be converted. For example your user identifier may be a Guid. It would be easier if you could access the converted value directly in your controller. 3) You may want to centralise other logic around particular attributes of the current identity.

This all leads to creating our own domain abstraction of the current identity. This could be an identity service for example:

public interface IIdentityService
{
  bool IsAuthenticated { get; }

  Guid UserId { get; }

  string Username { get; }

  IEnumerable<Claim> Claims { get; }

  // specialised properties
  // e.g. int AuthenticationLevel { get; }
}

This can now be injected as an instance into our controller:

public class MyController
{
  private readonly IIdentityService identityService;

  public MyController(IIdentityService identityService)
  {
    this.identityService = identityService
      ?? throw new ArgumentNullException(nameof(identityService));    
  }

  [HttpGet]
  [Route("example")]
  public IActionResult Get()
  {
    var userId = this.identityService.UserId;
    // etc.

    return this.Ok();
  }
}

This is now very easy to stub - you can create an implementation of IIdentityService and inject it into your controller, or if using an IoC container, registering your type against that service.

In order to create a production implementation, we can inject an instance of ClaimsPrincipal. This can retrieve the user identifier and username from the appropriate claims, depending on the authentication mechanism you are using. For example, if using OAuth/Open ID Connect, you can inspect the sub and name claims:

public class ClaimsIdentityService : IIdentityService
{
  private Guid? userId;

  private string username;

  public ClaimsIdentityService(ClaimsPrincipal principal)
  {
     this.principal = principal ??
       throw new ArgumentNullException(nameof(principal));          
  }

  public bool IsAuthenticated => this.principal.Identity != null
    && this.principal.Identity.IsAuthenticated;

  public IEnumerable<Claim> Claims => this.principal.Claims;

  public Guid UserId
  {
   get
   {
    if (this.userId.HasValue)
    {
     return this.userId;
    }

    var claim = this.Claims.Single(c => string.Compare(
      c.Type,
      "sub",
      StringComparison.OrdinalIgnoreCase) == 0);

    if (!Guid.TryParse(claim.Value, out var nameIdentifierGuid))
    {
      // not a valid GUID, throw new exception here ...
    }

    return nameIdentifierGuid;
   }
  }

  public string Username
  {
   if (!string.IsNullOrWhitespace(this.username))
   {
     return this.username;
   }

   var claim = this.Claims.Single(c => string.Compare(
    c.Type,
    "name",
    StringComparison.OrdinalIgnoreCase) == 0);

   this.username = claim.Value;
   return this.username;
  }
}

The ClaimsPrincipal in ASP.NET Core is available from the registered IHttpContextAccessor, so we can wire this up within our IoC container. For example, if using Autofac:

builder.Register(ctx => new ClaimsIdentityService(
  ctx.Resolve<IHttpContextAccessor>()?.HttpContext?.User))
  .As<IIdentityService>();

The IHttpContextAccessor is not registered by default, so you must register the service as a singleton within your startup ConfigureServices method:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();