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 topic many of you will have to deal with when working with JWTs: claims transformation.
This is something you end up having to do frequently given the different ways that each identity provider emits their claims, which in many cases do not match what your application expects.
So today I'll show you two ways to transform the claims you receive in your JWTs so they match what you need.
Let's dive in.
The problem
Let's say your ASP.NET Core API backend requires a specific scope claim to authorize access to most of its endpoints.
To achieve that we have defined our authorization policy as a fallback policy like this:
Making it a fallback policy means it applies to all endpoints by default, requiring a value of gamestore_api.all in the scope claim that should come in the access token.
The only problem is that the identity provider we are using, Keycloak in this case, is sending the scopes like this in the access token:
So it is including both the profile and email scopes there, along our gamestore_api.all scope, but it puts all of them in a single string.
ASP.NET Core should be smart enough to find our scope in that string, no?
Sadly no, which we can tell from our logs when trying to send a request with that access token:
It fails because it expects our scope to have the value of gamestore_api.all, and nothing else.
We can fix this either by configuring our identity provider to emit the claim the way we need it or by adding code to our app to transform the received claim into what we need.
Since many times we can't make that kind of modification to the identity provider, it's good to learn how to do this by writing some code in our app.
Let me show you a couple of ways to do this:
Using IClaimsTransformation
IClaimsTransformation is an interface that can be used to add extra claims to your ClaimsPrincipal, which contains all the claims that came with the access token.
Here's one implementation that can help us solve our issue:
As you can see, all we do is grab the current scope claim, split it into individual values for each scope, remove that claim, and then add one new scope claim for each of the individual scopes.
Then, you register your new class with the service container:
With that in place, the authorization policy check will succeed since the claims now show up like this:
That all works great when you have only one authentication scheme, Keycloak in this example, but when you have more than one it's not an ideal solution.
Say you have configured both Keycloak and Entra ID schemes. In that case, the claims transformation code will run for claims coming from both identity providers, even when they are going to be very different, which complicates things.
That's when I prefer to use the second approach.
Transforming claims via OnTokenValidated
This second approach is very similar to the first one, with the main difference being that the class we will create will be tied to a specific authentication scheme.
Here's the class:
As you can see, it's almost the same thing as before, but we start from a TokenValidationContext object instead and figure out the rest from there.
Just like before, we will register this class with the service container:
But then comes the key part. We add a handler for the OnTokenValidated event when configuring our Keycloak JWT bearer scheme:
With that, every time a token produced by Keycloak is validated, our transformer code will execute and fix the scope claim.
And if you later need to add another scheme, like Entra ID, you can add a new claims transformer that will work with the specific claims used by that identity provider.
Mission accomplished!
Wrapping up
If you would like to know more about Keycloak integration with ASP.NET Core, I got all that covered in course 3 of the bootcamp.
Now I'll get back to reinstalling this Windows box and hope I don't miss anything important along the way :)
Until next time!
Julio
Whenever you’re ready, there are 3 ways I can help you:
- .NET Cloud Developer Bootcamp: Everything you need to build production-ready .NET applications for the Azure cloud at scale.
- 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.
- Promote yourself to 20,000+ subscribers by sponsoring this newsletter.