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

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

  • What's New for Python, Java in Visual Studio Code

    Microsoft announced March 2024 updates to its Python and Java extensions for Visual Studio Code, the open source-based, cross-platform code editor that has repeatedly been named the No. 1 tool in major development surveys.

Subscribe on YouTube