Git Code Commit (all commits between last post and this commit (link))
Welcome back! Today, I am going to talk about some pre-work I am doing to decide how The Globe In My BucketList Application (TGIMBA) Application Programmatic Interface (API) should be done. This blog post will be about authentication and authorization. There are many posts on these topics, so I will just be speaking about what I learned and what I am likely do. I am doing this work on the FullStackDeveloper_Net code base that I have used for other posts. It is a microcosm and represents (mostly) where I see TGIMBA going.
In the previous version of TGIMBA, I posted a username and password to an API endpoint. If valid, I issued a token that expires. Until it expired, any API method could be called. However, I evaluated the token inside each API end point. Its a bit redundant. Plus, I want to use more of the piping that .NET Core provides.
So, what did I learn? Its complicated 🙂 I went through a lot of tutorials (please see ‘References’) that explain different aspects of .NET Core authentication and authorization. In my research, I came up (again, heavily based on the existing tutorials cited below) multiple approaches with varying degrees of correctness. Let’s dive in!!
First, I re-visited what I call ‘Classic .NET’ to see what had been possible (please see ‘ClassicNetWebApi’ project in the full stack solution (based heavily on reference #1)). I found you could create an authentication filter, read the token out of the header and decide if the user should be given access. I should also note that is not really authentication. Reading a token implies authorization and the token was generated by some other authentication code. The caveat to this is that some places just generate a standard token (i.e secret) that subscribers can use. So, its authentication (ish) 🙂
Pretty straight forward. You decorate your controller class with the authentication handler name.
Inside the handler, you read the token. If the token is correct, you get access.
If not, you get a 401.
.NET Core Authentication Middle ware Style
When I started on the .NET core version, I was dismayed to learn the IAuthenticationFilter approach no longer worked. But, I found that you can fake it with a middle ware approach. Using the same authentication (ish) approach as above, I plugged in a middle ware handler that does essentially the same thing (please see ‘NetCoreAPIAuthenticationMiddleWare’ project in the full stack solution (based heavily on reference #10)).
You list the middle ware as a dependency in Startup.cs (everything (it seems) is done in Startup.cs)):
Then, you add your handler and it is called for every controller. It reads the token and if it matches, access. If not, 401. This does not use the [Authorize] decorator.
.NET Core Authorization Filter
This option is close to the Classic .NET approach, but using IAuthorizationFilter. Nothing is added to Startup.cs. You decorate the controller with the handler’s name.
In the handler, you again read in the token. If correct, access. If not, 401.
.NET Core JWT
You decorate your controller with one of two versions of [Authorize].
- No policy specified – With this option, any claim that is part of the token will allow access.
- A policy specified – With this option, a specific claim must exist in the JWT for access to be allowed.
The claims are made in the authentication portion. For this, I just added a token endpoint that takes a login object with a username, password and data point value. A ‘data point value’ is a place holder for some value added property like customer id that can be passed/accessed. I will need this ability to easily identify a user in the TGIMBA API. In the two screen shots above, you can see where I access this data point value. I do nothing with username and password since this is for illustration and I pass the data point value into the token generation method.
I like the JWT version the best and it is likely what I will use. Why? It seems like its more inline with Microsoft best practices. The .NET Core team is pursuing a claims based authorization approach. While I miss IAuthenticationFilter because I like being able to read the header myself and it is a bit annoying to be told not to use this approach, I am doing this to learn and be in line with the intent of the framework. In one post, a member of this team specifically said they do not want us writing customer authorization handlers. Plus, JWT is fairly standard.