Identify users and their permissions with JWTs and ASP.NET Core WebAPI

March 1, 2018 · 5 minute read · Tags: core | security | webapi

Which user?

When you use JWTs (Json Web Tokens) to secure your ASP.NET Core Web API you can restrict parts of your API to authenticated users (by requiring a valid JWT for certain requests).

In the last post we saw how to block unauthorised users from accessing your API actions and conversely how to let authenticated users through.

But what about when you need a bit more information? Maybe you need a few more details about the user because George has access to delete users but Susan doesn’t. We don’t want to accidentally let Susan delete everyone from the system!

Getting hold of user details

In the last post we issued a token which included this claim.

var claims = new[]
{
    new Claim(ClaimTypes.Name, request.Username)
};

When this JWT is returned to the user it includes their username as a claim.

This means, when we get that token back in a subsequent task we can access that name.

[HttpGet("Test")]
public IActionResult Test()
{
    var userName = User.Identity.Name;

    return Ok($"Super secret content, I hope you've got clearance for this {userName}...");
}

User.Identity.Name is built in to ASP.NET Core and gives us a handy way of getting hold of that Name claim value.

This is all well and good, but the user’s name is only useful up to a point, what about knowing which parts of the system they can and can’t access?

It’s all in the claims

This is where you might have used roles in previous versions of ASP.NET but now we have claims and they’re pretty powerful.

Claims represent what the user is, not what they can do.

This is an important distinction.

Rather than try and store all the “roles” that a user might have (e.g. administrator, user, super user) you can store information about the user as claims.

A real-world example might help clarify this distinction.

Imagine you got home from work and found a card through the letterbox indicating that a delivery driver had attempted delivery of your parcel. The instructions indicate you should take the card, plus proof of identity to the local depot to collect your parcel.

So off you go to the local depot. When you get there they ask to see proof of your address. You grab your driving license and hand it over. They inspect it, check the address and if it matches the one on the card, give you your parcel.

Your driving license doesn’t include information about any relevant roles you might have, it doesn’t say that you’re a legitimate “Parcel picker-upperer” (because that’s a thing!) but rather it holds a claim about you, your address.

It’s up to the person doing the checking to decide if this identity (your driving license) and claim (your address) permits you to collect your parcel.

Going back to our example of George and Susan.

Let’s say our ficticious company has a policy whereby new staff members cannot delete customer accounts, but once they’ve received certain training they can.

To facilitate this we’ll need to know whether they’ve had that training, so let’s invent a simple claim for CompletedBasicTraining.

If they have this claim, they can delete users, if they haven’t they can’t.

To use this we need to do a few things.

  1. Issue the claim when the user requests a token
  2. Check for the claim when a request is made to the “DeleteUser” action

Step 1 is easy enough…

When someone requests a token, we can check the user database see if they have completed basic training, if so add the relevant claim.

var claims = new[]
{
    new Claim(ClaimTypes.Name, request.Username),
    new Claim("CompletedBasicTraining", "")
};

That second parameter is the claim value.

We could put something here like “true” if they have completed basic training and “false” if not.

However, the change we’re about to make will simply check for the existence of that claim so it’s simpler to not bother with a value and just say if it’s there they’ve completed training, if it isn’t they haven’t.

How do we use this claim?

We need a policy

So the company’s policy is that users who haven’t completed basic training cannot perform certain tasks.

We can model this exact policy in startup.cs when we set up authorization.

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        // omitted for brevity
        });

    services.AddAuthorization(options =>
    {
        options.AddPolicy("TrainedStaffOnly",
            policy => policy.RequireClaim("CompletedBasicTraining"));
    });

    services.AddMvc();
}

Nice, now we have a policy which we can apply to our controllers and actions as we see fit.

[Authorize(Policy = "TrainedStaffOnly")]
[HttpPost("DeleteUser")]
public IActionResult DeleteUser(string username)
{
    // go wild, delete the user, do what you have to...
    return Ok();
}

Caveat - Claims are in the token

Something to watch out for.

When you issue claims with a token, the claims are included in the token.

This means if you issue a token for Brian and he has the CompletedBasicTraining claim, but then you realise he cheated at the test, he will continue to make requests using his token which still has the CompletedBasicTraining claim.

He will continue to be able to do things like deleting customers until his token expires and he has to request a new one, at which point his updated status in the system would mean he was issued a new token without the CompletedBasicTraining claim (or maybe even a new EmploymentTerminated claim!).

Summary

Claims and policies are pretty powerful and (in many cases) a better fit for complex auth scenarios than roles.

With policies based on claims, you (the application developer) can easily tweak or add policies without having to re-issue tokens or change the details you hold for any users in your system.

With roles, if you invented a new role you would have to ensure all the relevant people were assigned to that role. With policies you can easily define a new policy and if it relies on existing claims you don’t need to change anything about your users, their tokens or their claims.

You can also get into some more sophisticated scenarios using something called custom policy handlers.

For example, maybe our company changes their policy to allow any employee who’s been employed for 3 months or more to delete customers, we could create a claim to hold the “EmploymentCommenced” date and set up a new custom policy to calculate if they’ve been employed long enough.

More on that in the next post.

Join the Practical ASP.NET Newsletter

Ship better Blazor apps, faster. One practical tip every Tuesday.

I respect your email privacy. Unsubscribe with one click.

    Next up

    But, which flavor of ASP.NET?
    Blazor, Razor, MVC… what does it all mean?!
    There’s a buzz about Blazor - Here’s why
    People are talking about Blazor but will it ever really replace JS?
    Starting out with the ASP.NET Core React template (part 3 - Separating out the frontend)
    How to run the frontend and backend parts of the ASP.NET Core React project template separately