Practical ASP.NET

Working with Claims to Authorize Users in ASP.NET Core and Blazor

When you need to integrate authorizing the user to perform some activity (or just want to retrieve information about the current user), you need to work with the ClaimsPrincipal’s Claims objects. Here’s everything you might want to do.

In earlier posts, I've discussed how to authorize a user declaratively both in ASP.NET Core and Blazor using the Authorize attribute, among other tools (and I've also referenced Eric Vogel's posts on authenticating users in ASP.NET Core against local resources here and here). Sometimes, however, declarative authorization isn't enough – it's typically very coarse-grained and locks users out of entire Controllers, Action methods, and components. Sometimes, you need something more fine-grained that integrates authorization with your business logic.

One Caveat: Nothing in this area has changed since ASP.NET MVC, so if you're familiar with claims-based authentication in .NET Framework 4.* then you can skip the rest of this post.

Ignoring Claims
For code-based, procedural authorization, you need to access your application's ClaimsPrincipal object (and I've also shown how to do that in both ASP.NET and Blazor). Once you have the ClaimsPrincipal object you can do quite a lot with it ... or, more accurately, with the claims associated with it. The only other source of authorization information is the ClaimsPrincipal object's Identity object, but it only has two properties that are useful: IsAuthenticated and Name. Even then, the Name property is really just a shortcut for retrieving the value of a Name claim.

Still, that IsAuthenticated property is useful.This code, for example, allows only logged in users to update Customer objects (assuming the variable called principal is pointing to my ClaimsPrincipal object):

if (principal.Identity.IsAuthenticated) {
    CustRepository.UpdateCustomer(cust);
} 

Working with Claims
For anything other than checking that the user has been authenticated, you'll want to work with the ClaimsPrincipal's Claims collection. Fortunately, you don't have to go searching through that collection for the claims that you're interested in.

For example, checking for a user's role is sufficiently common that there's a special shortcut method for it: the IsInRole method. Passed a role name, the IsInRole method searches through the Claims collection for role-based claims and returns true if a claim with that role is found. This code only lets users do updates if they're in the ProductManager role:

if (principal.IsInRole("ProductManager")) 
{
    CustRepository.UpdateCustomer(cust);
} 

Sometimes you're only interested in whether a user has a particular claim, regardless of what value is assigned to it. For example, any user with a company Id is obviously an employee, so you might just want to check to see if a claim exists for the user's Id. If so, the HasClaim method accepts a lambda expression that lets you test for the existence of a particular claim.

There are a ton of typical claims defined as constants in the static ClaimTypes class (they actually hold URIs defined in a SOAP standard for standard claim types). Ideally, whatever process set up those claims has used those types. For example, this code lets only company employees update customers by checking for the standard NameIdentifier claim type:

if (principal.HasClaim(c => c.Type == ClaimTypes.NameIdentifier))
{
    CustRepository.UpdateCustomer(cust);
}

Often, though, knowing a claim is present isn't enough -- you also want to know what value is assigned to the claim. You could use the previous version of the HasClaim method and just make its lambda expression more complicated. However, there's an overload of the HasClaim method that accepts both the claim type and the value the claim must have and then returns true if the claim both exists and has the value.

As an example, this code only lets the employee with the Id of phv update customer objects:

if (principal.HasClaim(ClaimTypes.NameIdentifier, "phv"))
{
    CustRepository.UpdateCustomer(cust);
}

You're not limited to predefined claim types. This code checks for a claim type with the name Division and only lets user in the Western division update customers:

if (principal.HasClaim("Division", "Western"))
{
    CustRepository.UpdateCustomer(cust);
}

Extracting Claims Information
As I said earlier, claims also provide a way of sharing user information throughout your application in a consistent way. You can also use the object to retrieve information about the user. The obvious piece of information to retrieve is the user's name using the ClaimsIdentity object's Name property:

string name = principal.Identity.Name;

However, you also have the option of retrieving any claim and using the related value. This code attempts to retrieve the user's Email claim and use its Value property. Of course, it's possible that the user doesn't have a Email claim so you should use the ? operator, as I do here, to check for a successful retrieval before asking for the claim's Value property (and check that you found something before using the value).

This code extracts the user's email address to use (I assume) in creating an email:

string returnAddress = principal.Claims.FirstOrDefault(
             c => c.Type == ClaimTypes.Email)?.Value;
if (returnAddress != null)
{
    SendEmail(returnAddress, subject, body, attachments);
}

As I said in my earlier posts on declarative security, you might be able to handle all of your authorization needs without writing a line of code. When that isn't enough, though, access in code to your user's claims may give you the flexibility you need.

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

  • 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.

  • TypeScript Tops New JetBrains 'Language Promise Index'

    In its latest annual developer ecosystem report, JetBrains introduced a new "Language Promise Index" topped by Microsoft's TypeScript programming language.

Subscribe on YouTube