---
title: "Debugging JwtBearer Claim Problems in ASP.NET Core"
description: "Debug missing claims in ASP.NET Core JwtBearer: inspect the access token, MapInboundClaims, the name and role claim types, and a claims diagnostic endpoint."
url: https://nestenius.se/net/debugging-jwtbearer-claim-problems-in-asp-net-core/
date: 2023-06-02
author: "Tore Nestenius"
category: ".NET"
tags: ["authentication"]
site: https://nestenius.se/
---

# Debugging JwtBearer Claim Problems in ASP.NET Core

A common problem when protecting your ASP.NET Core APIs is that expected claims are not found in the user object. In this blog post, I will give you some ideas for how to diagnose these types of problems. If you have claim problems related to the OpenIDConnect handler, head over to the blog post about [Debugging OpenID Connect Claim Problems in ASP.NET Core](https://nestenius.se/net/missing-openid-connect-claims-in-asp-net-core/).

## **What is the purpose of the JwtBearer handler?**

We use the [**JwtBearer**](https://github.com/dotnet/aspnetcore/tree/main/src/Security/Authentication/JwtBearer/src) authentication handler to protect ASP.NET Web APIs. Its primary purpose is to look for an access token in the incoming request, and if one is found, validate it and create a user object of type ClaimsPrincipal. JwtBearer only deals with access tokens, and it is essential to remember that you should never try to send an ID or refresh tokens to APIs.

![Ovewview of the core purpose of the JwtBearer handler in ASP.NET Core](https://nestenius.se/_astro/aspnet-core-jwtbearer-handler-authorization-header-flow.DgUWieJz_ZzRsGK.webp)

## First problem: Are the expected claims inside the received access token?

Using a tool like [**Fiddler**](https://www.getfiddler.com/), you can capture the raw access token from the incoming request and then use a site like [**jwt.io**](https://jwt.io/) to inspect what the access token contains.

The token is typically included in the Authorization header, as shown below:

```http
GET https://api.tn-data.se:7001/api/payments HTTP/1.1 
Host: api.tn-data.se 
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjU1...
```

If the token is not found here, then you have problems with the client that sends the token and how to troubleshoot that is beyond the scope of this blog post.

## How can I see the received access token using .NET code?

If you can’t use a tool like Fiddler to capture the access token, then you can for example, add the following event handler to the JwtBearer handler:

```csharp
.AddJwtBearer(opt =>
{
    //...
    opt.Events = new JwtBearerEvents()
    {
        OnMessageReceived = msg =>
        {
            var token = msg?.Request.Headers.Authorization.ToString();
            string path = msg?.Request.Path ?? "";
            if (!string.IsNullOrEmpty(token))
            {
                Console.WriteLine("Access token");
                Console.WriteLine($"URL: {path}");
                Console.WriteLine($"Token: {token}rn");
            }
            else
            {
                Console.WriteLine("Access token");
                Console.WriteLine("URL: " + path);
                Console.WriteLine("Token: No access token providedrn");
            }
            return Task.CompletedTask;
        }
    };
});
```

(Alternatively, you can send the information to the log if writing to the console is not possible.)

A sample output when using the code above:

```text
Access token URL: /api/payments 
Token: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwODE2RTk1MUU5NEJDMjEzNzEzN0Y0MUNGN0YwRDg3IiwidHlwIjoiYXQrand0In0.eyJpc3MiOiJodHRwczovL2lkZW…
```

It is important to not include this code in production, as writing to the console can result in a performance hit and writing to the console is an expensive operation.

## How can I view the claims that the JwtBearer handler received?

To see a list of all the received [claims](https://tn-data.se/openid-connect/#claims), you can add the following event handler to JwtBearer:

```csharp
.AddJwtBearer(opt =>
 {
     //...

     opt.Events = new JwtBearerEvents()
     {
         //...

         OnTokenValidated = ctx =>
         {
             Console.WriteLine();
             Console.WriteLine("Claims from the access token");
             if (ctx?.Principal != null)
             {
                 foreach (var claim in ctx.Principal.Claims)
                 {
                     Console.WriteLine($"{claim.Type} - {claim.Value}");
                 }
             }
             Console.WriteLine();
             return Task.CompletedTask;
         }
     };
 });
```

Running this code, you should see a list of the claims extracted from the access token written to the console. For example, it could look like this:

```text
Claims from the access token
iss - https://localhost:6001
nbf - 1682863284
iat - 1682863284
exp - 1682923284
aud - payment
aud - invoice
aud - order
scope - openid
scope - profile
scope - email
scope - employee_info
scope - api
http://schemas.microsoft.com/claims/authnmethodsreferences - pwd
client_id - missingjwtbearer-claims-client
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - 2
auth_time - 1682863282
http://schemas.microsoft.com/identity/claims/identityprovider - local
name - Bob Smith
seniority - Senior
contractor - no
http://schemas.microsoft.com/ws/2008/06/identity/claims/role - ceo
http://schemas.microsoft.com/ws/2008/06/identity/claims/role - finance
http://schemas.microsoft.com/ws/2008/06/identity/claims/role - developer
sid - C3768153BD5988F9B8B56D4A3AD518A1
jti - 5DB88EE876EF1B449E86930CE747AF3B
```

As you might notice above, some claims have been renamed to what Microsoft thinks the claims should be named.

You can disable this renaming by setting this flag to false:

```csharp
.AddJwtBearer(opt =>
{
    // ...
    opt.MapInboundClaims = false;
    // ...
});
```

If you set the MapInboundClaims flag to false, then the output in my sample application changes to:

```text
Claims from the access token
iss - https://localhost:6001
nbf - 1682863284
iat - 1682863284
exp - 1682923284
aud - payment
aud - invoice
aud - order
scope - openid
scope - profile
scope - email
scope - employee_info
scope - api
amr - pwd
client_id - missingjwtbearer-claims-client
sub - 2
auth_time - 1682863282
idp - local
name - Bob Smith
seniority - Senior
contractor - no
role - ceo
role - finance
role - developer
sid - C3768153BD5988F9B8B56D4A3AD518A1
jti - 5DB88EE876EF1B449E86930CE747AF3B
```

## Fixing the ASP.NET Core username and roles properties

The next problem is fixing the **name** and **roles** of the user. Again, this is a common problem that I see on Stack Overflow.

To demonstrate this, I have created the following API action method:

```csharp
[Authorize]
public ActionResult<Result> Get()
{
    var result = new Result();

    result.Name = User?.Identity?.Name ?? "Unknown Name";
    result.IsInRoleCEO = User?.IsInRole("ceo");

    result.Claims = User?.Claims.Select(c => c.Type + ":" + c.Value)?.ToList();

    return Ok(result);
}

public class Result
{
    public string? Name { get; set; }
    public bool? IsInRoleCEO { get; set; }
    public List<string>? Claims { get; set; }
}
```

The Name and IsInRoleCEO does not work. Why?

The short answer is that Microsoft has a different opinion of what the name of the claims should be when it looks for the name and role.  

By default, it will look for these two claim types:

- `http://schemas.microsoft.com/ws/2008/06/identity/claims/name`
- `http://schemas.microsoft.com/ws/2008/06/identity/claims/role`  

They are defined in the .NET source code, in [**ClaimTypes.cs**](https://source.dot.net/#System.Security.Claims/System/Security/Claims/ClaimTypes.cs,1f8b9c14a9b34152)

To verify this, you can add the following code to a controller method, and it will print the name of the role/name claim type that it looks for:

```csharp
var user = (ClaimsIdentity)User.Identity;
Console.WriteLine("Name claim type: " + user.NameClaimType);
Console.WriteLine("Role claim type: " + user.RoleClaimType);
```

This is the output:

```text
Name claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
Role claim type: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
```

To fix this, we need to set the name of your name/role claim to match what the claims are named inside the token. For example, in my case, the claims are named **name** and **role**:

```csharp
.AddJwtBearer(opt =>
{
    // ...
    opt.TokenValidationParameters.RoleClaimType = "role";
    opt.TokenValidationParameters.NameClaimType = "name";
    // ...
});
```

With the code above in place, the code below should start to work as expected:

```csharp
result.Name = User?.Identity?.Name ?? "Unknown Name";
result.IsInRoleCEO = User?.IsInRole("ceo");
```

In my example application, they will return:

```text
…
"name": "Bob Smith",
"isInRoleCEO": true,
…
```

## Final ASP.NET Core code:

The code for the API startup and sample controller:

Program class:

```csharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(opt =>
    {
        opt.Authority = "[MyAuthority]";
        opt.Audience = "myaudience";

        opt.TokenValidationParameters.RoleClaimType = "role";
        opt.TokenValidationParameters.NameClaimType = "name";

        opt.MapInboundClaims = false;

        opt.Events = new JwtBearerEvents()
        {
            OnMessageReceived = msg =>
            {
                var token = msg?.Request.Headers.Authorization.ToString();
                string path = msg?.Request.Path ?? "";
                if (!string.IsNullOrEmpty(token))
                {
                    Console.WriteLine("Access token");
                    Console.WriteLine($"URL: {path}");
                    Console.WriteLine($"Token: {token}rn");
                }
                else
                {
                    Console.WriteLine("Access token");
                    Console.WriteLine("URL: " + path);
                    Console.WriteLine("Token: No access token providedrn");
                }
                return Task.CompletedTask;
            }
            ,

            OnTokenValidated = ctx =>
            {
                Console.WriteLine();
                Console.WriteLine("Claims from the access token");

                if (ctx?.Principal != null)
                {
                    foreach (var claim in ctx.Principal.Claims)
                    {
                        Console.WriteLine($"{claim.Type} - {claim.Value}");
                    }
                }
                Console.WriteLine();

                return Task.CompletedTask;
            }
        };
    });
```

Payment controller:

```json
[Route("api/[controller]")]
[ApiController]
public class PaymentsController : ControllerBase
{
    [Authorize]
    public ActionResult<Result> Get()
    {
        var user = (ClaimsIdentity)User.Identity;
        Console.WriteLine("Name claim type: " + user.NameClaimType);
        Console.WriteLine("Role claim type: " + user.RoleClaimType);

        var result = new Result();

        result.Name = User?.Identity?.Name ?? "Unknown Name";
        result.IsInRoleCEO = User?.IsInRole("ceo");

        result.Claims = User?.Claims.Select(c => c.Type + ":" + c.Value)?.ToList();

        return (result);
    }
}

public class Result
{
    public string? Name { get; set; }
    public bool? IsInRoleCEO { get; set; }
    public List<string>? Claims { get; set; }
}
```

## Claims diagnostic endpoint

It can be useful to create a diagnostic endpoint for debugging purposes that simply returns the claims found in the User object. Here’s a sample endpoint for ASP.NET Core:

```json
[Authorize]
[Route("/api/tokendiagnostics")]
public IActionResult GetClaims()
{
    var result = new TestResult()
    {
        Name = User.Identity?.Name ?? "Unknown Name",
        Claims = (from c in User.Claims select c.Type + ":" + c.Value).ToList()
    };

    return new JsonResult(result);
}

public class TestResult
{
    public string? Name { get; set; }
    public List<string>? Claims { get; set; }
}
```

## Conclusions

I hope the above steps can help you troubleshoot your JwtBearer claim problems. If you have any suggestions for this blog post, feedback, questions, or would like to get in touch, you can find my contact details on the [contact page](https://nestenius.se/contact/).

## Related posts

- [Debugging OpenID Connect claim problems in ASP.NET Core](https://nestenius.se/net/missing-openid-connect-claims-in-asp-net-core/)
- [IdentityServer – IdentityResource vs. ApiResource vs. ApiScope](https://nestenius.se/net/identityserver-identityresource-vs-apiresource-vs-apiscope/)
- [Troubleshooting JwtBearer authentication problems in ASP.NET Core](https://nestenius.se/net/troubleshooting-jwtbearer-authentication-problems-in-asp-net-core/)  
- [Improving ASP.NET Core Security By Putting Your Cookies On A Diet](https://nestenius.se/net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/)

## Related Training Workshops

- [Building ASP.NET Core APIs](https://tn-data.se/courses/building-asp-net-core-apis/) 
- [Web security fundamentals](https://tn-data.se/courses/web-security-fundamentals/)
