Practical ASP.NET

Accessing and Extending Authorization Claims in ASP.NET Core and Blazor

When you need to integrate authorization with procedural code, you're going to need your application's ClaimsPrincipal object so that you can check the user's authorization claims. Here's both how to get to the ClaimsPrincipal and how to extend it with custom claims.

If you've written code to access user information in ASP.NET MVC, you've worked with the ClaimsPrincipal object. That object is still necessary in ASP.NET Core but the way you access it has changed. In ASP.NET MVC there was an assumption of stability that ASP.NET Core doesn't count on. You could, for example, in ASP.NET MVC retrieve the ClaimsPrincipal object from the thread your Action method was executing on. In the asynchronous world of ASP.NET Core, where your request may start on one thread and finish on another, that's not a viable option.

So in this post, I'll start by showing how to get the ClaimsPrincipal object both in ASP.NET Core and in Blazor. However, the reason you want to get to the ClaimsPrincipal object is to access the claims that are associated with it.

That's because claims perform two functions. Primarily, of course, claims provide authorization information which is why you need them for authorization code. However, claims also act as carriers for useful information about the current user. Since claims are available wherever the ClaimsPrincipal object is available (and, as you'll see, the ClaimsPrincipal object is available everywhere), claims let you access user-related information without having to make a trip to some datastore. So I'm also going to show how your application can add claims to the ClaimsPrincipal object to make it more useful both not only to your authorization process but to any other process that requires user-related information.

Retrieving the ClaimsPrincipal Object in ASP.NET
Getting the ClaimsPrincipal object in a Controller or in the model class for a Razor Page is relatively easy: Just use the base class' User property, like this:

    ClaimsPrincipal cp = this.User;

For any class that has access to ASP.NET Core's HttpContext class, the process is equally simple because the HttpContext object also has a User property that holds a ClaimsPrincipal object.

In any component that you add to your application's services collection (i.e. in the Startup class' ConfigureServices method), you can ask for the IHttpContextAccessor object in the object's constructor. While the IHttpContextAccessor object doesn't have a User property, it does give you access to the HttpContext object which has a User property. To use the IHttpContextAccess object, you first need to add it to your application's services collection in your ConfigureServices method:

services.AddHttpContextAccessor();

Then, in your service class' constructor you can ask for the IHttpContextAccessor object and use it to get the HttpContext object and its User property. Typical code might look like this:

private HttpContext hcontext;
public MyController(IHttpContextAccessor haccess)
{
  hcontext = haccess.HttpContext;
}

public IActionResult ContactUs()
{
   ClaimsPrincipal cp = hcontext.User;

Getting the ClaimsPrincipal Object in Blazor
In Blazor, the process for getting the ClaimsPrincipal object is a little more complicated. In Blazor, the user's authorization information is cascaded down to you through the CascadingAuthenticationState component. To grab that information, which is of type Task<AuthenticationState>, you need to set up a CascadingParameter property in the component where you want to authorize the user.

In this code, I've defined that parameter and called the property authState:

[CascadingParameter]
private Task<AuthenticationState> authState { get; set; }

The AuthenticationState object that this property grabs has a User property that holds the ClaimsPrincipal object you need.

The earliest point in the life cycle of the component where you can use a CascadingParameter is your component's OnParametersSet method. This code overrides that method, grabs the AuthorizationState object from the Task object and extracts its User property into a ClaimsPrincipal field called principal (it also checks to see if the authState property is null first, in case no authorization information has been cascaded down to the component):

private System.Security.Claims.ClaimsPrincipal principal;
protected async override void OnParametersSet()
{
  if (authState != null) 
  { 
     principal = (await authState).User;
  }

Adding Claims
If claims are as useful as I'm saying they are then, after a user is authenticated (see Eric Vogel's posts on authenticating users here and here), you may want to retrieve information about the user and add it to the ClaimsPrincipal object's Claims collection. For example, it might be important to processing logic throughout your application to know what country the user is from. One way to handle that requirement would be to add a country claim to the ClaimsPrincipal object that represents the current user.

The way to do this that works with all the authentication methods that ASP.NET Core supports is to create a class that implements the IClaimsTransformation interface. Adding that interface to a class obliges you to add the TransformAsync method to your class (the TransformAsync method will automatically be called by ASP.NET after the user is authenticated and will be passed the ClaimsPrincipal object representing the current user). The method must return the ClaimsPrincipal object wrapped inside a Task object so that makes the method a candidate for the Task object's FromResult method.

A basic IClaimsTransformation class, then, might look like this:

public class UserInfoClaims : IClaimsTransformation
{
   public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
   {
      return Task.FromResult(principal);
   }
}

The TransformAsync method may be called multiple times in processing a request so you don't want to do anything in here if you don't have to -- especially, you don't want to add any claims that you may have added during a previous call to this method. So the first thing you should do is check to see if the claim that you want to add is already present.

As an example, this code checks to see if the user already has a Country claim before doing anything:

if (!principal.HasClaim(c => c.Type == ClaimTypes.Country))
{

}
return Task.FromResult(principal);

You can't actually add claims directly to the ClaimsPrincipal's Claims collection. Instead, you create a new ClaimsIdentity object, add claims to it, and then add that ClaimsIdentity object to the ClaimsPrincipal object (claims added to any ClaimsIdentity automatically appear in the ClaimsPrincipal's Claims collection). There are a variety of ways to add Claim objects to a ClaimsIdentity object (for example, you can pass a collection of Claim objects to the ClaimsIdentity object's constructor). However, in this example, I'm only going to add a single Claim so I'll do it with the ClaimsIdentity object's AddClaim method:

if (!principal.HasClaim(c => c.Type == ClaimTypes.Country))
{
   ClaimsIdentity id = new ClaimsIdentity();
   id.AddClaim(new Claim(ClaimTypes.Country, "Canada"));
   principal.AddIdentity(id);
}

With your IClaimsTransformation object built, the last step is to add it to your application's services collection. That means adding code like this to the ConfigureServices method of your application's Startup class:

services.AddScoped<IClaimsTransformation, UserInfoClaims>();

Now that you've retrieved the ClaimsPrincipal object and added whatever claims to it you need, you can start using it. I'll cover that in my next post.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube