Secure ASP.NET Core Web API using JWT Authentication

Secure-Web-API-using-JWT-Authentication

In this tutorial you will learn how to secure ASP.NET Core Web API using JWT Authentication in .NET 5, I will try to simplify this topic step-by-step while coding.

We will build two endpoints, one for the customers’ login and one to get customer orders. The APIs will be connected to an SQL Server Express database all running on the local machine.

What is JWT?

JWT or JSON Web Token is basically a way to format tokens, which represent an encoded structure of data that is compact, url-safe, secure and self-contained.

JWT authentication is a standard way to communicate between APIs and clients, so both parties can make sure that the data being sent/received is trusted and verified.

JWTs should be issued by a server and digitally sign it using a cryptographically secure secret, so that it will make sure that any attacker won’t be able to tamper the payload sent within the token and impersonate the legit user.

JWT structure includes 3 parts, seperated with dots, each of which is a base64 url-encoded string and formatted in JSON:

Header.Payload.Signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwicm9sZSI6IkFjY291bnQgTWFuYWdlciIsIm5iZiI6MTYwNDAxMDE4NSwiZXhwIjoxNjA0MDExMDg1LCJpYXQiOjE2MDQwMTAxODV9.XJLeLeUIlOZQjYyQ2JT3iZ-AsXtBoQ9eI1tEtOkpyj8

Header: represents the algorithm used to hash the secret (example HMACSHA256)

Payload: the list of data or claims to be transferred between client and api

Signature: the hashing of the concatenation of Header and Payload )

Because the JWT tokens are base64 encoded, you can simply explore them using jwt.io or through any online base64 decoder.

And for this particular reason, you should never save confidential information about the user within JWT.

Tutorial Prerequisites

Download and Install the latest update of Visual Studio 2019 (I am using Community Edition), the Dot Net Core SDK 5.x will already come bundled with latest version of VS2019.

Download and Install the latest updates of SQL Server Management Studio and SQL Server Express

Let’s start our tutorial

Let’s create a new Project in Visual Studio 2019.

Give your project a name like SecuringWebApiUsingJwtAuthentication

We need to choose ASP.NET Core Web Api Template, then press Create.

Visual studio will now create the new ASP.NET Core Web API template project for you.

Let’s remove the WeatherForecastController.cs and WeatherForecast.cs files so that we can start creating our own controllers and models

The end result project structure should look like the below:

Preparing the Database

Once you’ve installed SQL Server Express and SQL Management Studio on your machine, you should be able to access the host normally through the below pop-up dialog

Now from the object explorer, right click on Databases and choose new database, give your database a name like CustomersDb.

To make the process easier and faster, you just need to run the below script, it will create the tables and insert the needed data into the CustomersDb.

Preparing the Database Models and DbContext

Create Entities Folder, and then add Customer.cs

Then add Order.cs

I added the JsonIgnore Attribute to the Customer object in order to hide it when doing Json serialization of the Order object.

The JsonIgnore Attribute comes from System.Text.Json.Serialization namespace, so make sure you include it at the top of the Order class.

Now we will create a new Class that inherits from the DbContext of EFCore that will be used to map to our database

Create a class with name CustomersDbContext.cs

Visual studio will start throwing errors now, because we need to reference NuGet Packages for EntityFramework Core and EntityFramework Sql Server

So right click on your project name and choose manage NuGet Packages, then browse and downloaded the below packages

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.SqlServer

Once the above packages are references in your project, you should no longer see errors from VS

Now head to Startup.cs file and add our dbcontext into the service container inside the ConfigureServices function

Let’s open appsettings.json file and include the ConnectionStrings Section with our connection string name

Now we are done with the database mapping and connection part. We will move on to prepare our business logic within services.

Creating the Services

Create a new folder with name Requests

We will have a LoginRequest.cs class here that will represent the username and password fields that the customer will provide to login.

And for this we need a special Response object, to be returned for the valid customer which will include basic user info and their access token (in JWT format) so that they can pass it within their subsequent requests to the authorized APIs through the Authorization Header as a Bearer token

So create another folder with name Responses and inside it create a new file with name LoginResponse.cs

Create a new folder with name Interfaces

Add a new interface ICustomerService.cs, this will include the prototype for the customer login method

Now comes the part of implementing the ICustomerService

Create a new Folder and name it Services

Add a new class to it, with name CustomerService.cs

The above login function checks in the database for the active customer’s username, password, if these conditions match, then we will generate a JWT and return it in the LoginResponse for the caller, otherwise it will just return a null value in the LoginReponse.

First, let’s create a new folder with name Helpers

Add to it a class with name HashingHelper.cs

This will be used to check the hash for the password in the login request to match the hashed password on the database alongside the hashing salt.

Here we are using a key-based derivation function (PBKDF2) which it applies the HMac function in combination with a hashing algorithm (SHA-256) to the password with a salt (base64 encoded random number with size 128-bit) and repeats this as many times as specified in the iterations parameter (10000 times is the default) in our example to derive a random key from the produced result.

Key Derivation functions (or Password-Hashing functions), such as PBKDF2 or Bcrypt require a longer computation time and more resources for a password to get cracked or brute-forced because of the large number of iterations applied along with the salt, this is called key stretching

Note: You should never save a password in the database as-is (plain text), always make sure to calculate and save the hash of the password, and use a Key Derivation function based hashing algorithm with a large key size (i.e 256-bit or more) and random large salt (64-bit or 128-bit) to make it very difficult to crack the passwords.

Also you should make sure to apply validation rules of strong passwords (combinations of alphanumeric and special characters) whenever you are building user registration screens or pages, as well as password retention policies, this will even maximize the level of security for the stored passwords.

Generate JSON Web Token (JWT)

Add another class to the Helpers folder with name TokenHelper.cs

This will include our token generation function

We need to reference another library here which is the Microsoft.AspNetCore.Authentication.JwtBearer

Let’s take a closer look at the GenerateToken function

We are passing the customer object, we can use as many properties as we want and add them to the claims that will be embedded within the Token. But for this tutorial, we will be just embedding the id property of the Customer.

The JWT relies on digitally signing algorithms, one of these algorithms that is recommended and we are using here is the HMac Hashing algorithm using a 256-bit key size.

We are generating the key from a random secret that we previously generated using the HMACSHA256 Class. You can use any random string, but make sure to use a long and hard to guess text, and better go with the HMACSHA256 class as illustrated in the previous code sample.

You can save the generated secret in a constant or appsettings and load it within the Startup.cs file

Creating the Controllers

Now we need to consume the Login method of the CustomerService from the CustomersController

Create a new folder and name it Controllers

Add a new file CustomersController.cs, it will have one POST method that will accept the username and the password and return the JWT token along with other customer details if the login process was successful otherwise it will return 400 bad request

As you can see here, we have define one POST method /Login that accepts the LoginRequest( username and password), it does a basic validation on the input, and it calls the Login method of the customer service

We will be injecting the CustomerService through the Controller’s constructor using the public Interface ICustomerService, we will need to define this injection in the Startup’s ConfigureServices function

Now, just before running the api, we can configure the launching url and also we can know the port number for both http and https within the IIS Express object under IIS Settings.

This is how should your launchsettings.json file look:

Now if you run the Api on your local machine, you should be able to call the login method and generate your first JSON Web Token

Testing Login on Postman

Keep the browser open, and open postman

In a new request tab, put down the localhost and port number provided to you in the settings once you run the application

from the body, choose raw and JSON and put the below JSON object that you will be using to login to the customers database through our RESTful API

Here is the request/response from postman:

Here we have our first JWT.

Let’s prepare our API to receive this token, validate it, find a claim in it and then return a response for the caller.

There are multiple ways that you can use to validate your APIs and authorize your users:

  • Policy-based authorization, which can also include defining Roles and Requirements, as per the dot net core team, this is the recommended approach to implement the API Authentication through a fine-grained approach. You can read more about Policy-based authorization on this Stackoverflow thread.
  • Having a custom middleware that validates the JWT passed in the request headers on the APIs that have the Authorize attribute decorated on them.
  • Having a custom attribute set on one or more Controller methods that validates the Request Headers Collection for the JWT Authorization header.

In this tutorial, I will be using the Policy-based authentication in its simplest form, just to show you you can apply the policy-based approach in securing your ASP.NET Core Web APIs.

The difference between Authentication and Authorization

Authentication is the process of verifying that a user has the rights to access your APIs. Usually an unauthenticated user trying to access your APIs will receive an http 401 Unauthorized Response

Authorization is the process of verifying if the authenticated user has the proper rights or permissions to access a specific API. Usually an unauthorized user trying to access your API that is only valid for a specific role or requirement will receive an http 403 Forbidden Response

Configuring Authentication and Authorization

Now let’s add the authentication and authorization configurations in our startup file

Inside the ConfigureServices method, we need to define the authentication scheme and its properties and then we will define the authorization options.

In the authentication part, we will be using the Default Scheme of the JwtBearer, and we will define the TokenValidationParamters, so that we validate the IssuerSigningKey to make sure that the JWT in question is signed using the correct Security Key with the same Secret

In the authorization part, we will be adding a policy that, when specified on an endpoint with the Authorize attribute, it will authorize only the customers whose accounts are not blocked.

A blocked customer will still be able to access other endpoints that don’t have the policy defined on them, but for the endpoints who define the OnlyNonBlockedCustomer policy, blocked customers will be denied access with a 403 Forbidden response.

This policy will be validated through a custom requirement that implements the IAuthorizationRequirement and a handler that inherits the AuthorizationHandler<T> class where T is the customer requirement class

First create a folder and name it Requirements.

Add a new class with name CustomerStatusRequirement.cs

Then create another folder and name it Handlers.

Add a new class with name CustomerBlockedStatusHandler.cs

Finally, let’s add all the authentication and authorization configurations to the services collection

For these we need to include the following namespaces:

Now the above won’t work by itself, the authentication and authorization have to be included within the ASP.NET Core API pipeline in the other startup method which is the Configure method

So here we are done with configuring our ASP.NET Core Web API using JWT Authentication.

You can check the official docs for more information about Policy-Based Authorization in ASP.NET Core

Creating the OrderService

We will need a new service that will specialize in the orders.

Create a new Interface under Interfaces Folder with name IOrderService.cs

The interface includes one method that will retrieve the orders for a given customer by Customer Id

Let’s implement this interface.

Create a new class under Services Folder with name OrderService.cs

Creating the OrdersController

Now we need to create a new endpoint that will use the Authorize attribute with the OnlyNonBlockedCustomer policy

Add a new Controller under your Controllers Folder, name it OrdersController.cs

We will create a GET Method that will be used to retrieve the Customer’s Orders. This method will be decorated with the Authorize Attribute and defined an access policy for only the Non Blocked Customers,

That’s our requirement for this policy;

Any blocked customer who will try to get their orders, even if the customer is authenticated properly, will receive a 403 Forbidden request, because this customer is not authorized to access this specific endpoint.

We need to include the OrderService within the Startup.cs file

Add the below just under the CustomerService line

This is the full view of the Startup.cs file to double-check with yours.

Testing on Postman

Run the application and open Postman

Let’s try to login with a wrong password

Now let’s try to login with correct credentials

If you take the above token and validate it on jwt.io, you will see the header and payload details in it:

Now let’s test the get orders endpoint, we will take the token string and pass it in the Authorization Header as Bearer Token

So why did our API return a 403 Forbidden?

If you go back one step before, you will notice that in the claims our customer is blocked (“IsBlocked”: True), which tests our policy and requirement for this endpoint that only non blocked customers are authorized to access this endpoint

To make this work, we will either unblock this customer or try to login with another customer. So let’s go unblock this customer

Head back to your database, and change the user’s Blocked value to False

Now open Postman again and login with the same user, so that we get a new JWT that includes the updated claim value for IsBlocked claim type.

Now, let’s peak at our new JWT in jwt.io

Do you notice the difference now? This customer is no longer blocked now, because we obtained a new JWT that includes the refreshed claim that is reading from the database.

So let’s try to access our secure endpoint using this new JWT

It works now! Our customer has successfully passed the policy’s requirement for being a non blocked customer and thus the orders are now showing.

Let’s see what will happen if a user tries to access this endpoint without passing the Authorization header

or a malicious user tried tampering with the JWT

JWT is tamper-proof, so no one can fool around with it.

I hope this tutorial provided you a good understanding of API Security and JWT Authentication. You should now be able to Secure ASP.NET Core Web API using JWT Authnetication.

Please let me know if you have any question or comments.

You can find the full source code on Github

Mukesh Murugan has also blogged about this important topic in a very detailed and comprehensive way, feel free to check his article as well here: Build Secure ASP.NET Core API with JWT Authentication – Detailed Guide

Check my other articles related to ASP.NET Core:

Summary

API Security is a rather important and crucial topic that should be handled properly to guarantee proper authentication and authorization of the users to properly access your backend resources and protect your data from unauthorized access.

JWT is a very common and easy way to protect APIs in a standard, url-safe and cross-platform methodology. Claims are safely passed through the JWT between communicating parties.

Now that you have learnt how to secure ASP.NET Core Web API using JWT Authentication in .NET 5, please feel free to share it across your network using the social media buttons on the left.

Bonus

For this great topic, I have chosen a great piece of music for my great readers.

Enjoy the brilliant tunes of Henry Purcell: Ground in C Minor by Hanneke van Proosdij, harpsichord

7 thoughts on “Secure ASP.NET Core Web API using JWT Authentication

    1. Thanks for your comment, I am glad you you found it useful, please share it with your network (using the social media buttons on the left) and subscribe to be the first to receive the blog’s updates.

  1. Can you amend this with a method to add a new user? I haven’t done much work with the MS crypto libraries to hash the password/salt.

    Also can you do another article that authenticates with an existing user in Azure AD?

    1. Thanks for your comment. Sure, I will update it with a method to add new user in the coming few days. And yes I will soon prepare an Azure AD authentication tutorial, please make sure to subscribe, if not already, to be the first to receive the updates 🙂

      1. Just subscribed. Found this while studying how to work with Azure AD B2C. Looking forward to seeing your approach for adding a new user… a very important piece of the puzzle.

  2. Great article. However I have one question. When I have an open (no Authorize attribute) it works great. Once I add the Authorize attribute and send the Bearer token in the Authorization header, I am getting a 302 which then redirects to a /Account/AccessDenied which then returns a 404. Despite the /Accounts/AccessDenied does have an open get. Not sure what I am missing here.

Leave a Reply

%d bloggers like this: