Token Authentication in WebAPI is pretty Smart & Simple! In this In-Depth Guide, letās learn How to Secure ASP.NET Core API with JWT Authentication that facilitates user registration, JWT Token Generation, and Authentication, User Role Management, and more. You could use this demonstration as a boilerplate template to secure your future/existing APIs with ease. I will leave the link to the GitHub repository at the end of this article.
Disclaimer: This is a quite detailed guide with more than 3000 words. Do not forget to grab a beer/coffee and bookmark this page before continuing_! :D_
The Need to Secure APIs
Since WebApis are stateless, securing them cannot depend on the server sessions. Each request to the concerned API endpoint must contain credential like data that specifically authenticates/authorizes users to access the API data. These credential like data are often placed in the HTTP Headers of the Request Message. There are various ways to Authenticate ASP.NET Core API. In this Guide letās build a Secure ASP.NET Core API with JWT Authentication.
What is JWT?
This is one of the most commonly used techniques to secure APIs, allowing users to access resources they are authorized to.
Structure of JSON Web Token
Header
Usually contains the details on type of Token (JWT) and the algorithm used to sign the token, such as RSA, SHA256.
Payload
This is the most important section of the JWT. It contains the claims, which is technically the data we are trying to secure. Claims are details about the user, expiration time of the token, etc
Signature
For this section, it is an encryption between the header, payload and a secret key.
You can visit https://jwt.io/#debugger-io to decode, verify and play around with JSON Web Tokens. PS - You would be able to decode JWTs only if you have an actual valid JSON Web Token and a Secret key which is never exposed to the outside world.
Securing ASP.NET Core API with JWT Authentication - Core 3.1
Letās start implementing JWT Authentication and Secure our ASP.NET Core API. As mentioned earlier, by the end of this article you will be able to register users, secure API endpoints, and weāll go through role-based Authorization as well. Here is the long list of features we will be implementing in this Solution
- Entity Framework Core - Code First Approach - I have an article regarding this, check it out!
- Seeding the Database
- Microsoft Identity
- User Roles
- Custom Implementation of IdentityUser Class
- API Endpoint to Register a user with email, username, password, firstname, lastname.
- API Endpoint to Generate a valid token for the user who requests with his/her credentials.
- Secured API Endpoint which can be accessed only by an Authenticated user (JWT).
- Adding Roles to Existing Users.
- Secured API Endpoint that demonstrates Role-based Authorization (Only by Admins)
- Testing the Endpoints with Postman
Once implemented, we will have a close-to-production ready Authentication / Authorization system for your ASP.NET Core Applications.
Setting up the Project
Create a new ASP.NET Core 3.1 Application with the API template. We will use Postman for sending requests to our secure API. I use Visual Studio 2019 Community as my go-to IDE for C# development.
Installing the Required Packages
JWT Settings
Letās add the Settings for JWT to our appsettings.json
Key - The Super Secret Key that will be used for Encryption. You can move this somewhere else for extra security.
IssuerĀ - identifies the principal that issued theĀ JWT.
AudienceĀ - identifies the recipients that theĀ JWTĀ is intended for.
DurationInMinutes - Defines the Minutes the generated JWT will remain valid.
Now create a new class, Settings/JWT.cs which will be used to read data from our previously created JWT Section of appsettings.json using the IOptions feature of ASP.NET Core.
Application User Model
Create a new class, Models/ApplicationUser.cs. Here we inherit from the IdentityUser Base Class and add additional properties like FirstName and LastName of the user.
Adding DbContext
We will keep this part simple. We will use the DbContext Class only for adding the Identity(User) Tables and nothing more. Create a class, Contexts/ApplicationDbContext.cs.
Adding the Connection String
Now, Add the ConnectionString of the Database you want to point to. Add these lines to appsettings.json
Seeding the Database
What is Seeding?
During development, we may need some sample data in our database to test the application. EF Core addresses this requirement by the Seed Functionality. It essentially means that we can push sample data to the database at defined entry events. In this scenario we will add default Roles and a default user to our database using this feature.
Now letās create a class, Constants/Authorization.cs in which we define the Supported Roles and a default user.
Line #3 to 8 - We define a Roles Enum of our Supported Roles, ie, Administrator, Moderator, User
Line #9 to 12 - We define the default user details. Note that the default user will have a User Role attached,
With that out of the way, Letās create another class, Contexts/ApplicationDbContextSeed.cs to add the default data.
We have made the seed class, but havenāt invoked it in our application yet. I prefer to call this seed class during the start of the application. For this, modify the Program.cs/Main class like the below.
Here we are calling the Seed class while the application starts. Once the application fires up, the default data will be posted to the database if these data doesnāt exists. You get the point yeah?
Secured Controller
For this demonstration, we will be securing this controller. <localhost>/api/secured
. We have added an Authorize Attribute to the top, which means that only authorized requests will be able to access the endpoint. We have an action method, GetSecuredData (a dummy method) which is to be secured by our API.
User Service
We will need a Services class that contains the Core User Functions like Register, Generate JWTs etc. Create a new Interface, Services/IUserService.cs
And itās concrete class, Services/UserService.cs. Let them be empty for now. We will add the functions whenever we need them further in this article.
User Controller
Create a Controller that consumes the UserService. Letās call this controller, Controllers/UserController.cs. Do Inject the UserService Interface as well.
Configuring Startup
Finally, letās modify the 2 functions of Startup.cs
Make sure that app.UseAuthentication(); always comes before app.UseAuthorization();, since you technically need to Authenticate the user first, and then Authorize.
Now letās configure the authentication , Add the context classes , userservice classes to our application.
Line #4 Adds the JWT Section from appsettings to our JWT Class.
Line 6 and 7 Adds Identity and User Service to the application.
Line #14 to 36 is for JWT Authentication. Letās go in detail.
Line #14 is a default extension in ASP.NET Core to add Authentication Service to the application.
Line #16 and 17 defined the default type of authentication we need, ie, JWT Bearer Authentication.
From Line #20 it is about configuring the JWT Bearer.
Line #22 defines if we need an HTTPS connection. Letās set it to false.
Line #32 to 32 reads the settings from appsettings.json and adds to the JWT Object.
Migrations & Updating the Database
Now that our context classes, required services, and connection strings are added, letās go and generate our database. Run the following commands on the Package Manager Console to apply the migrations.
add-migration āinitialā update-database
Once the operations are completed, you will get a āDoneā message on the console. Build and Run the Application. Open up SQL Server Object Explorer to investigate the created database. Letās check if the default user and Roles are added to the Database.
You can see the the database was created succesfully. Open up the AspNetUser Table. This is the table from Identity that store the user details.
Our default user was added. Letās now check if the Seeded Roles are added. For this open up the AspNetUserRoles tables.
Great, the roles are added as well. Letās now proceed with the actual tasks.
Registering a New User
Now, we need an endpoint to allow users to register. To this endpoint the user has to post his name,email and password. If these are valid, we register the user with the default role as User.
For this, letās create Models/RegisterModel.cs with the following properties. The user has to post data with this object to register.
In IUserService.cs, add a function definition to Register the user accepting a Register Model.
Go to the Concrete class, UserService to implement the Register Function.
Here, we take in the RegisterModel object, and create a new ApplicationUser model object from it. We will also have some kind of validation to check if the email is already registered with our api. If the email is not used already, we will proceed to creating the user and adding a Role āUserā.Else, letās send a message that says, āAlready Registered.ā
Letās call this method from an API endpoint. Go to Controllers/UserController and add this set of code. Here we take in the Register model and pass it to user service, which returns a specific string message.
Testing with Postman
Open up Postman and define a raw JSON object that is to be posted to <localhost>/api/user/register
You will get a message confirming user registration.
Requesting Secured Controller with Postman
Remember we have a secured Controller / endpoint at ../api/secured ? Letās try to access it via Postman.
As expected, we get a 401 Unauthorized Error. Why? Because we have not passed a valid JWT token to the endpoint yet. How do you get the token ? Well, letās build another endpoint that generates this JWT token for us.
Generate JWT Token
Now that we have completed Registration, letās try to fetch the JWT Token. We will build a Token Generation function that takes in a TokenRequestModel (email, password) , validates if the email and password , and build a token for us.
Start by creating Models/TokenRequestModel.cs
Now, create another class, Models/AuthenticationModel.cs which is basically the response from the endpoint. The endoint is supposed to return you a status message, user details, his/her roles and finally our token.
Go back to IUserService.cs and add a GetTokenAsync Method.
Letās implement it in our Concrete Class, Services/UserService.cs.
Line #3 Creating a new Response Object,
Line #4 to #10 checks if the passeed email is valid. Return a message if not valid.
Line #11 Checks if the password is valid, else returns a message saying incorrect credentials.
Line #14 Calls the CreateJWTToken function.
Line #20 returns the response object
Line #26 to 58 builds the JWT.
Line #28 gets all the claims of the user ( user details )
Line #29 gets all the roles of the user.
Line #51 to #57 creates a new JWT Security Token and returns them.
Now that our Service Function is done, letās wire it up with the User Controller. Add the following to Controllers/UserController.cs
Theoretically, on posting a valid email and password to ../api/user/token, We are supposed to receive a AuthenticationModel object containing user details and our precious JWT Token (that expires in 60 minutes). Fire up Postman and do the following.
You can see that, on posting valid credentials we have now received the token along with user detials. Letās post a invalid email and check the response.
Cool right? Letās post a valid email with a wrong password.
Pretty Secure now huh? Now letās copy the generated Token string and try to access the secured endpoint. In postman, go the Authorization Tab and Change the type to Bearer Token , Paste your Token and click on send. Now the 401 error goes away, You have successfully accessed the secure controller with JWT tokens.
Authorization
We are done with Authentication, letās go to Authorization.
Authentication vs Authorization.
Authentication means confirming your identity, proving that you are a user registered in the system. We have already done that.
Authorization means verifying that you as a user have enough permission to access a particular resource. In APIs, there can be endpoints that are accessible only for the Admins.
Letās add a new action method in our Controllers/SecuredController.cs. Letās build this method in a way that only the users who have an āAdministratorā Role can access. This can be easily done by adding a Role property to the Authorize attribute and specifying the allowed Roles.
When you try to POST to the secured endpoint with a valid JWT token, you can notice that we get a new error, 403 Forbidden. This means that you do not have enough permission to access this endpoint.
Adding Roles to User
Letās create our last function which is responsible to add Roles to a specified user. With this, you can upgrade the permissions of a user, so that he/she can become a Moderator / Administrator and access our secured endpoint.
Create a new Model class, Models/AddRoleModel.cs with the following Properties.
Add this to Services/IUserService.cs Interface.
Letās implement the AddRoleSync Function in Services/IUserService.cs
Line #10 to #17 is our core function if the user is a valid one.
Line #11 checks if the passed Role is present in our system. If not, throws an error message.
Line #15 adds the role to the valid user.
Finally, letās wire this function up with an action method in Controllers/UserController.cs
This route , addrole, takes in the email, password, and required role from the request object and passes it to the Service , which in turn adds the requested role to the user, if valid. Letās try to add a role, āModeratorā to the user.
Great, it works. Now try to add a invalid role to the user. Letās say āRandomRoleā.
Seem fine. Role RandomRole not found. Now, Letās elevate the userās permission to Administrator.
Thatās done too. Now we will need a new token, since the userās roles has changed quite a lot. The old token that we have will become invalid. Letās request for a new token.
WHile requesting for the token, you can have a glance at the Roles of the User. Now the user has User/Moderator and Admin roles attached. So hopefully, with this new token, we must be able to access our second restridcted endpoint. Letās check.
There you go! Now we have successfully completed Role Based Authorization as well. Thatās it for this Huge Article. Hope you have learnt everything clearly. Bookmark this page, so that you can come back and go through one more time. It takes more than one time to understand anything clearly ;)
Summary
In this Detailed guide we have covered the basics of JWT, Registering Users, Seeding Databases, Entity Framework Core - Code First, Generating JWTs, Securing Endpoint based on Roles, Adding Roles to Users.
In the next article, we will discuss Refresh Tokens, which makes our APIs even more secure. The article is published. You can check it here to learn more about Refresh tokens and itās implementation in ASP.NET Core APIs here.
Do you have any other queries suggestions for me? Feel free to leave them below in the comments section. Happy Coding :)