In this post, I would like to discuss about how we can use claims following a typed approach, this topic came about from a bug that was encountered during a code review for an application.
As such, I wanted to find a way of mitigating such issues in the future by using concrete classes and the rarely used functionalities of implicit casting.
But first off, let’s look at what Claims are and how they are used.
The github repository for the examples in this post can be found here.
Table of Contents
What are Claims?
If we look at the common definition for a claim in the context of authorization or identity, then a claim is a statement about a user or a role that is made by an application.
The common example for a claim, as seen in the wikipedia article, states that you can imagine your system as being a night club bouncer, and your user has a driver's license, which has been released by an authorized third party, that driver states a few claims about our user such as their date of birth, which the bouncer can then verify that their date of birth meets the criteria (be over 18 years old) and that the license has been emitted by the authorizing party.
Based on the analogy previously described, a claim can be issued by other trusted systems, and they serve as meta-data for a specific user, or in some cases for a specific role. This makes claims a lot more composable than a user just having roles to describe their permissions than just using role-based approaches.
In short, a claim is a piece of meta-data or attributes of a user that claims something about the user and that claim is issued by some party, it’s up to the system to determine if that party is trusted or not.
How to Create Claims in ASP.NET Core 2.1
Now we’re going to look at how to manipulate claims for both users and roles.
If you’re building an ASP.NET Core application, then you most likely will be using the Identity services provided by the library, which is a wrapper that adds several classes to the dependency injection container (Service provider). The class we are most interested in is UserManager<T>. Where T
represents our user entity type respectively.
For us to use the UserManager
, we will need to inject into the classes we want to use them into (several topics on this subject can be found in previous posts). Once we have our instance of the UserManager
, we can manipulate the claims by adding, removing, replacing claims as per documentation.
To create a claim, we will need to create an instance new Claim("ClaimType", "ClaimValue")
. Afterwards, using this instance, we can add it to users, or update existing claims.
More information about all the constructor overloads for claims can be found here. But as we can see, the claim type and the claim values are string
based, which brings us to our current topic.
Our Custom Claim
So for this example, I created a class called UserCurrentDateTimeClaim
that would represent a concrete claim I want to work within the example application. The class definition is as follows:
using System;
namespace TypedClaims.Controllers
{
class UserCurrentDateTimeClaim
{
private readonly DateTime _dateTimeValue;
public UserCurrentDateTimeClaim(DateTime dateTimeValue)
{
_dateTimeValue = dateTimeValue;
}
}
}
The point to illustrate here is the fact that our custom claim can only receive a DateTime
instance, of course, we could create claims as complex or as simple as required.
Now we need to have this class be able to pass off as a claim so that we can add it to a user without creating adaptors and other complex conversion mechanisms. So we will add an additional method that will implicitly cast out custom claim to a system claim.
using System;
using System.Security.Claims;
namespace TypedClaims.Controllers
{
class UserCurrentDateTimeClaim
{
private readonly DateTime _dateTimeValue;
public UserCurrentDateTimeClaim(DateTime dateTimeValue)
{
_dateTimeValue = dateTimeValue;
}
public static implicit operator Claim(UserCurrentDateTimeClaim userCurrentDateTimeClaim)
{
return new Claim(nameof(UserCurrentDateTimeClaim),
userCurrentDateTimeClaim._dateTimeValue.ToString());
}
}
}
The implicit
operator will let our system know how to convert our custom class into a Claim
type. So now, we can add this claim to a user like this await userManager.AddClaimAsync(currentUser, new UserCurrentDateTimeClaim(DateTime.Now));
.
The benefit of this approach is that we have a consistent way of adding a specific claim to a user that will respect the type of the data we want to store, the name of the ClaimType
and any other additional claim information we’re interested in like issuers.
Of course, if our claim has a bit more validation logic in it, or methods that work with the underlying stored type, we might also want to convert it back from a claim to our custom type, so we will add another conversion method that will do the reverse.
using System;
using System.Security.Claims;
namespace TypedClaims.Controllers
{
class UserCurrentDateTimeClaim
{
private readonly DateTime _dateTimeValue;
public UserCurrentDateTimeClaim(DateTime dateTimeValue)
{
_dateTimeValue = dateTimeValue;
}
public static implicit operator Claim(UserCurrentDateTimeClaim userCurrentDateTimeClaim)
{
return new Claim(nameof(UserCurrentDateTimeClaim),
userCurrentDateTimeClaim._dateTimeValue.ToString());
}
public static implicit operator UserCurrentDateTimeClaim(Claim claim)
{
return new UserCurrentDateTimeClaim(DateTime.Parse(claim.Value));
}
}
}
This way, once we have the claim we’re looking for (and we will see an example of that), we can convert it back to our own custom type and act upon it.
Example
With the custom claim class written before, I created a Post
action in the example application that makes use of our custom claim.
public async Task AddUpdateDateClaim([FromServices]UserManager userManager)
{
var currentUser = await userManager.GetUserAsync(HttpContext.User);
Claim updatedClaim = (await userManager.GetClaimsAsync(currentUser)).
FirstOrDefault(claim => claim.Type == nameof(UserCurrentDateTimeClaim));
if (updatedClaim is null)
{
await userManager.AddClaimAsync
(currentUser, new UserCurrentDateTimeClaim(DateTime.Now));
}
else
{
UserCurrentDateTimeClaim exampleOfConvertingToCustomType =
updatedClaim;
await userManager.ReplaceClaimAsync(currentUser, exampleOfConvertingToCustomType,
new UserCurrentDateTimeClaim(DateTime.Now));
}
return RedirectToAction("Index");
}
Conclusion
I hope you enjoyed my approach to using typed claims for eliminating the issues created by having claims stored as string
s, both for type and value.
With this approach, you could even save object in JSON or binary format and just serialize and deserialize them, and as long as the system is configured properly (thinking of json serialization options) and the stored value has not been tampered by other means, then you should have a consistent way of switching to and from your own claim classes.
As stated at the start, the
Thank you and see you next time.