IsAuthenticated Is Not About The User


IsAuthenticated Is Not About The User

Read in your browser

A few days ago I finished the audiobook version of Nexus, the latest book by Yuval Noah Harari, and wow, it's such an impressive take on the impact of AI in our society and what could come next.

One potential scenario mentioned in the book is the creation of AI-powered social credit systems, where governments or organizations score individuals based on their behaviors, decisions, and interactions.

Governments today are already collecting an insane amount of data from all of us all the time via surveillance cameras, online activities, social media, and even financial transactions.

What happens when you plug in a powerful AI model that can easily ingest all that data, analyze individuals' behaviors, and assign scores based on predefined "desirable" traits?

AI models could then predict future behaviors based on historical data, pre-emptively penalizing or rewarding individuals, meaning that people could be punished not for what they’ve done, but for what AI predicts they might do.

Crazy, right? The Nosedive episode of the Black Mirror Netflix series portrays this exact scenario. And it aired back in 2016!

I really hope we can master AI before it masters us :)

And, in the meantime, let's focus on mastering the ASP.NET Core platform by learning how to use those JWTs in our apps.

Let's dive in.

Validating JWTs in ASP.NET Core

Last week we looked at JWTs, their structure, and the role they play in token-based authentication. For reference, here's the decoded token we were looking at:

But how to use those tokens in an ASP.NET Core API?

We could write some code to read and decode those tokens ourselves, but it's best to use the built-in authentication middleware that is designed to do exactly that.

You can start by installing the Microsoft.AspNetCore.Authentication.JwtBearer NuGet package and then add these few lines to Program.cs:

The AddAuthentication call registers the core authentication services in the DI container, and AddJwtBearer configures the JWT Bearer authentication scheme so your application can accept and validate JWT tokens.

Now, we usually add those lines and move on to the next thing without thinking too much about what's going on.

But I think it's important to understand what AddJwtBearer is enabling for us behind the scenes:

  • Token Validation: It ensures that the JWT has not been tampered with by verifying its signature.
  • Issuer Check: It validates that the token's issuer (iss claim) matches the expected value (http://localhost:8080/realms/gamestore).
  • Audience Check: It validates that the token's audience (aud claim) matches the expected value (gamestore-api).
  • Expiration Check: It verifies that the token has not expired based on the exp claim.
  • Authentication middleware: In minimal APIs, it automatically adds the Authentication middleware, saving you from having to also call UseAuthentication.

And, if you are wondering about RequireHttpsMetadata = false, we need it because our issuer, Keycloak in this case, is running as a Docker container without TLS in our box, so we can't use HTTPS. This is safe for local dev, but a no-no in Prod.

With that out of the way, how do we receive and use the JWT info in our API endpoints?

Do we have a valid JWT?

The first thing you may want to check in your back-end API is if the request has been properly authenticated, meaning that a JWT was included in the Authorization header and that the token is valid.

We can do that by injecting and using the ClaimsPrincipal class in our API endpoint:

The ClaimsPrincipal user object represents the security context of the incoming request, while the Identity property there, which is a ClaimsIdentity object, is populated with all the claims extracted from the JWT.

What about that IsAuthenticated == false check? Would true mean that the user has been authenticated?

Not at all!

In a protected back-end API, IsAuthenticated doesn't confirm that the user exists, is active, or has appropriate permissions in your application. It simply reflects the validity of the JWT at the time of the request.

Remember that here we are in the back-end, not in a front-end application. All we have is an access token in the form of a JWT, which tells us what the client is authorized to do.

The token might include user-related claims, but it doesn’t guarantee the user's current state in your system.

In other words, IsAuthenticated is purely about the validity of the access token, not the current state of the user or even whether a user is involved.

OK, but how to read the info in the decoded JWT?

Reading JWT claims

Assuming we got a valid JWT (IsAuthenticated == true), reading the claims is fairly straightforward.

For instance, here's a code snippet to grab either the Email claim or the Sub claim from the JWT:

All that will do is pull out the value from the corresponding claim from the user.Identity.Claims collection, which in this case will help us calculate the user ID based on either the email or the ID assigned to the user in the identity provider, as reported in the access token.

Now, many times when you try to read simple claims like that you quickly find out that both them return null.

How can that be?

Disabling the default claims mapping

What exactly is getting populated in that Claims collection of the Identity property? If using Keycloak as your identity provider, it may look like this:

Which means that the two claims we are trying to query in code actually have a different name:

sub => http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier

email => http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress

That is why we can't read those claims (and a few others), but why is this happening?

Well, those strange claim types exist primarily for historical and compatibility reasons, and they were meant to help integrate with Microsoft's broader ecosystem (as of who knows how many years ago).

ASP.NET Core is automatically mapping our raw claims to these legacy claim types that most identity providers don't use today, especially in the context of the modern OAuth 2.0 and OIDC protocols.

How to fix this? Simple, just set MapInboundClaims to false in your AddJwtBearer call:

Now when your endpoint receives a valid JWT, you will see a different set of claims:

And your code will no longer have trouble reading those email and sub claims. Done!

Now there are more known issues with reading claims, like the role claim coming in the wrong place (which screws up role-based authorization) or the scope claim landing as a single string as opposed to an array of strings (which complicates claims-based authorization).

If you are facing those issues, check out the bootcamp, where I go over the right way to deal with them.

The ASP.NET Core Security course launches this Tuesday!

I know many of you struggle with all sorts of security aspects in your ASP.NET Core apps, and in this third bootcamp course, I'm spending more time than in any of my previous courses going over all the security topics, patterns, protocols, and best practices you must know to ensure your apps are well protected from unauthorized access.

This is also the last course on Stage 1 of the bootcamp, which completes what I think is the first major milestone every .NET cloud developer should reach, which is mastering the ASP.NET Core platform from the very essentials, across must-know advanced concepts and all the way to key security aspects like JWT and OpenID Connect.

Watch out for the new course launch email early this Tuesday!

Cheers,

Julio


Whenever you’re ready, there are 3 ways I can help you:

  1. .NET Cloud Developer Bootcamp:​ Everything you need to build production-ready .NET applications for the Azure cloud at scale.
  2. Ultimate C# Unit Testing Bundle: A complete beginner to advanced C# unit testing package to write high-quality C# code and ship real-world applications faster and with high confidence.
  3. Promote yourself to 20,000+ subscribers by sponsoring this newsletter.

11060 236th PL NE, Redmond, WA 98053
Unsubscribe · Preferences

The .NET Saturday

Join 20,000+ subscribers for actionable .NET, C#, Azure and DevOps tips. Upgrade your skills in less than 5 mins every week.

Read more from The .NET Saturday

Fixing Claims the Right Way Read in your browser It's flu season in the US and after my 5yo went down with a cold, sure enough, one of my other kids followed and then it got me. So it's been a tough week, but hope to be recovering by the time you read this. On top of that, seems like it's time for a full Windows reinstall because this box has been giving me so much trouble lately. But before getting into that adventure, I thought it would be best to finish this newsletter, where I'll cover a...

Avoiding The DIY Authentication Trap Read in your browser With Course 3 of the bootcamp finally launched, I'm now switching gears to Course 4, which will be all about deploying .NET apps to the Azure cloud, one of the most exciting parts of the bootcamp. One key thing you need to master before doing any sort of cloud development is Docker, which I believe most developers are not using yet. That's why I spent most of this week working on tons of new Docker-specific content, learning a few new...

Understanding JSON Web Tokens Read in your browser Well, 2024 is gone, and wow it was such an amazing year in many aspects, but especially for software developers and AI enthusiasts. As I look back, here are a few 2024 breakthroughs that I think set the stage for how we will be doing things moving forward: GPT-4o and Claude 3.5 Sonnet enabled more natural and versatile human-AI interactions, significantly changing the way we solve our everyday human problems. AI coding assistants like GitHub...