Understanding identity in .NET
Tags: security, beginners, dotnet, csharp
Published: 2022-10-02 15:35:53

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](https://www.reddit.com/r/dotnet/comments/we9qx8/a_comprehensive_overview_of_authentication_in/) 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](https://en.wikipedia.org/wiki/Universally_unique_identifier) can be a good one.

### Building our first claims

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

```csharp
var subjectId = new Claim("sub", "E4WlUXrJgy4");
var name = new Claim("name", "Alice"),
var role = new Claim("role", "Admin"),
```

> For complex types, the the [ValueType](https://learn.microsoft.com/en-US/dotnet/api/system.security.claims.claim.valuetype?view=net-6.0) 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:

```csharp
// 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);
```

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

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

Console.WriteLine(aliceIdentity.Name);
// null
```

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
1. Out identity cannot correctly identify Alice's name

#### Fixing IsAuthenticated

If we take a closer look at [the available constructors](https://learn.microsoft.com/en-US/dotnet/api/system.security.claims.claimsidentity?view=net-6.0#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:

```diff
- var aliceIdentity = new ClaimsIdentity(claims);
+ var aliceIdentity = new ClaimsIdentity(claims, "pwd");
```

We can now test if it worked:

```csharp
Console.WriteLine(aliceIdentity.IsAuthenticated);
// True
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfyvuemz62rwo74xm0zp.png)

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

```diff
- var aliceIdentity = new ClaimsIdentity(claims, "pwd");
+ var aliceIdentity = new ClaimsIdentity(claims, "pwd", "name", "role");
```

We can now test if it worked:

```csharp
Console.WriteLine(aliceIdentity.Name);
// Alice
```

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:

```csharp
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);
```

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

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

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

However, please notice that `IsInRole` is case sensitive!

```csharp
Console.WriteLine(principal.IsInRole("Citizen"));
// False
```

### 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:

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

var unknown = principal.FindFirst("address");
// null
```

### 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:

```diff
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"),
};
```

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6z2b2jxf631m108u355o.png)

> Also note that in this case, calls to `IsInRole` with either or the role will return true!
> ```csharp
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:

```csharp
var hasAliceName = principal.HasClaim("name", "Alice");
// True
```

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

```csharp
var aliceName = principal.HasClaim(claim
    => claim.Type == "name" && claim.Value == "Alice");
```

## 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](https://unsplash.com/@sheldonkennedy?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/closed-door?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)