DEV Community 👩‍💻👨‍💻

DEV Community 👩‍💻👨‍💻 is a community of 964,423 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Understanding identity in .NET
Pierre Bouillon
Pierre Bouillon

Posted on

Understanding identity in .NET

Understanding how .NET is manipulating the concept of identity can be very harsh and confusing, especially for beginners, when jumping right to the docs to get started.

In this post, we will try to understand the underlying concepts on which the identity is built with a simple example that we will build step by step, let's get started!

If you already are familiar with the basics and want to deep dive into ASP.NET security, I strongly recommend you to have a look at this awesome post on Reddit about it

Claim - Identity's most atomic type

A bit of theory

Claims are the simplest entity that is used for identity, they simply are a wrapper for a key and a value according to an issuer, which all of them are strings.

Each of those parts is carrying a certain meaning.

The key is the type of the value held by the claim. For example, if we want the claim to wrap the name of the user, this key could be "name". Since this field is just a string, you can put whatever value you want in it.

The value is simply the value of the claim (duh). This field is also a string and as such all other types will have to be serialized.

The issuer is the authority, the system which has delivered this claim. For example, if you are using GitHub to login, GitHub will emit a claim indicating your identifier. Similarly, your system can deliver its own claim.

Another way of seeing a claim is to consider it as a statement. Here are some examples:

  • Alice has an id of 123 according to GitHub
  • The current user's name is Bob according to the system

A special claim

Despite the claim structure being fairly simple and permissive, there is one claim that is really vital: the subject id.

The subject id is a claim representing the user. It must respect three requirements:

  • Be unique - two users of the same system should not be identified by the same identifier
  • Be immutable - if a user's identifier changes over time things can go south pretty quickly
  • Should not be reused - otherwise you could end up with a new user also being identified as a former one which should have been deactivated

The type for the claim is commonly abbreviated sub

As an example, the name of a user is not a good subject id: if two users are named "Bob", it will break the first requirement.

On the other hand, a GUID can be a good one.

Building our first claims

To create a claim, we just have to provide a key and a value:

var subjectId = new Claim("sub", "E4WlUXrJgy4");
var name = new Claim("name", "Alice"),
var role = new Claim("role", "Admin"),
Enter fullscreen mode Exit fullscreen mode

For complex types, the the ValueType parameter can be used to help for the deserialization

ClaimsIdentity - Creating the identity

If you grasp what claims represent, the rest of the post should be pretty simple.

The ClaimsIdentity type is only a wrapper for a bunch of claims, which make sense: the identity of a person is based on several statements about him.

As an example, Alice's identity is the grouping of some things about her:

  • According to the government, her name is Alice
  • According to the government, her role in the country is citizen
  • According to the government, her height is 1m and 63cm

Creating the Alice's identity

In .NET, it will be pretty much the same thing:

// Statements about Alice
var claims = new List<Claim>
{ 
    new Claim("sub", "E4WlUXrJgy4"),
    new Claim("name", "Alice"),
    new Claim("role", "citizen"),
    new Claim("height", "1m63cm"),
};

// Alice's identity according to those statements
var aliceIdentity = new ClaimsIdentity(claims);
Enter fullscreen mode Exit fullscreen mode

And voilà, we materialized Alice's identity. With it, we can get some information about her:

Console.WriteLine(aliceIdentity.IsAuthenticated);
// False

Console.WriteLine(aliceIdentity.Name);
// null
Enter fullscreen mode Exit fullscreen mode

Wait, we did not specified that at all!

Since we created her identity and specified her name, she should be authenticated and have "Alice" as her name.

There are two issues here:

  1. Our identity is not aware that Alice is authenticated
  2. Out identity cannot correctly identify Alice's name

Fixing IsAuthenticated

If we take a closer look at the available constructors, we can wee that it is possible to provide a string representing the authentication type.

Since we did not, it was set to null by default and our identity assumed that, if the user has not used any authentication method, he cannot be authenticated.

Let's add the authentication method:

- var aliceIdentity = new ClaimsIdentity(claims);
+ var aliceIdentity = new ClaimsIdentity(claims, "pwd");
Enter fullscreen mode Exit fullscreen mode

We can now test if it worked:

Console.WriteLine(aliceIdentity.IsAuthenticated);
// True
Enter fullscreen mode Exit fullscreen mode

Done, let's fix the other issue

Retrieving her name

When we were looking at the constructors, you might have noticed that some of them had a nameType parameter.

In fact, if we do not specify the name of the types used for the name and the role of the user, it will be defaulted to some XML value that does not look like "name" at all:

Image description

Let's specify both those types and see if it has fixed our problem:

- var aliceIdentity = new ClaimsIdentity(claims, "pwd");
+ var aliceIdentity = new ClaimsIdentity(claims, "pwd", "name", "role");
Enter fullscreen mode Exit fullscreen mode

We can now test if it worked:

Console.WriteLine(aliceIdentity.Name);
// Alice
Enter fullscreen mode Exit fullscreen mode

Yay!

ClaimsPrincipal

Since we have our identity, we can now create what .NET is using to authenticate our user: the ClaimsPrincipal.

Using Alice's identity, doing so is pretty straightforward:

var claims = new List<Claim>
{ 
    new Claim("sub", "E4WlUXrJgy4"),
    new Claim("name", "Alice"),
    new Claim("role", "citizen"),
    new Claim("height", "1m63"),
};

var aliceIdentity = new ClaimsIdentity(claims, "pwd", "name", "role");

var principal = new ClaimsPrincipal(aliceIdentity);
Enter fullscreen mode Exit fullscreen mode

This is this object that is exposed by HttpContext.Current.User

Based on this, we can assert what our user role is:

Console.WriteLine(principal.IsInRole("citizen"));
// True
Enter fullscreen mode Exit fullscreen mode

However, please notice that IsInRole is case sensitive!

Console.WriteLine(principal.IsInRole("Citizen"));
// False
Enter fullscreen mode Exit fullscreen mode

Retrieving a single claim of a given type

To retrieve a claim of our identity, we can use FindFirst that will give us either the the value of the claim with this type, or null if there is no claim with this type:

var name = principal.FindFirst("name");
// Alice

var unknown = principal.FindFirst("address");
// null
Enter fullscreen mode Exit fullscreen mode

Retrieving all claims of a type

Since everything is based on a collection of claims, several claims can be of the same type.

In our case, Alice may have several roles and nicknames:

var claims = new List<Claim>
{
    new Claim("sub", "E4WlUXrJgy4"),
    new Claim("name", "Alice"),
    new Claim("role", "citizen"),
+   new Claim("role", "medic"),
    new Claim("height", "1m63"),
};
Enter fullscreen mode Exit fullscreen mode

To retrieve all of them, you can use the method FindAll:

Image description

Also note that in this case, calls to IsInRole with either or the role will return true!

Console.WriteLine(principal.IsInRole("citizen"));
// True
Console.WriteLine(principal.IsInRole("medic"));
// True

Other assertions

If you want to verify that the current principal identity contains a specific claim, you can do so:

var hasAliceName = principal.HasClaim("name", "Alice");
// True
Enter fullscreen mode Exit fullscreen mode

For a more complex assertion, you may instead use a predicate:

var aliceName = principal.HasClaim(claim
    => claim.Type == "name" && claim.Value == "Alice");
Enter fullscreen mode Exit fullscreen mode

Wrapping up

In this post we covered the atoms from which the ClaimsPrincipal is made

Of course this is rarely used as such but I hoped it gave you a little bit more understanding of some of the classes used to manipulate the concept of identity!


Photo by Sheldon Kennedy on Unsplash

Top comments (0)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.