Initial Authentication and Identity Setup
In this video, I'll show you how to use ASP.NET Core Identity to implement authentication in your APIs. In .NET 8, we got some exciting improvements for ASP.NET Core Identity which make it easier to use. So let me show you how we're going to implement authentication support.
We are starting literally from scratch and we're going to implement authentication support using ASP.NET Core Identity. I'm going to start by adding authorization services and I'm going to continue this by adding authentication services. I'm also going to configure cookie authentication because it's much simpler, and I need to specify my authentication scheme. I'm going to access IdentityConstants which comes from the Microsoft.AspNetCore.Identity namespace, and we're going to specify the application scheme for the AddCookie method. Then I'm going to say builder.Services.AddIdentityCore and I need to specify my identity user.
Now, I don't have this type at the moment, so I'm going to create it, and I'll add it inside of the database folder. I'm going to call my entity the User. And what I have to do to make this compatible with the Identity system is to implement the IdentityUser base class. If we take a look at the source code for the IdentityUser class, you'll see that it's implementing the generic IdentityUser class. And if we take a look at that, you can see that the IdentityUser comes with a lot of properties that we need to implement authentication and various scenarios for authorization. So if we go back to our User, we also have the ability to extend the IdentityUser type by just defining additional properties on the User class. So let's say I want to also store my user's initials. I would define a string property and it's going to be added to the database when we create a respective EF Core migration.
Configuring the EF Core Database Context
And having said that, we also need to introduce our EF Core database context. So I'm going to add a few NuGet packages that we are going to need. I will look for Entity Framework and first of all, I'm going to install the Npgsql provider because I want to connect to Postgres. I'm also going to install the Microsoft.EntityFrameworkCore.Tools package that's going to allow me to create my migrations. And I'm going to update my search here to also look for Entity Framework Identity, and we need to install Microsoft.AspNetCore.Identity.EntityFrameworkCore. This is going to contain some abstractions that we can use to define our Identity context.
So with these NuGet packages installed, let's go back to our implementation. And I'm going to add my Application Database Context. What we have to do to make this work with Identity is to implement the IdentityDbContext base class. The generic argument is going to be our User, which is the identity user. And then let me define the constructor. So I'm going to pass in an instance of the DbContextOptions and I'll specify my ApplicationDbContext as the argument. And then let's pass this options instance to our base constructor. I'm going to leave this like this for the time being and let's go back to our Program file.
Wiring Up Identity, EF Core, and Migrations
Now I can use the user as the argument for the AddIdentityCore method. And the next step is to configure Entity Framework as the database for my identity system. All I need to do is to specify my database context. And I'm also going to call AddApiEndpoints which is going to configure the required services for the identity endpoints, which were added in .NET 8.
I'm going to add some helpers that we are going to use to run migrations. So just a simple extension method that's going to resolve the database context from a scope and then run migrations. So let me call this method if I'm running in a development environment. And we also need to map the identity endpoints by calling MapIdentityApi and you specify your identity user, which is our User type.
I also have to configure EF Core, so I'm going to say AddDbContext, specify my database context type, and then I'm going to connect my database instance with Postgres by calling UseNpgsql and I need to just provide my connection string. So I'll say builder.Configuration.GetConnectionString and I'll specify the connection string called database. The database connection string is going to connect to my Postgres instance running inside of a Docker container and the database name is called identity.
Now, let's apply a few more customizations to our database context. I'm going to overwrite the OnModelCreating method. And it's important to leave in place the base call to the OnModelCreating because it's going to configure a lot of the entities that the Identity DB context uses, but I want to make a few more changes. So let's start by configuring our user entity and I want to set the property that I configured as a custom property, which is the Initials property. I want to say that it has a max length of five, which is going to save us some storage space. Also, I want to configure the default schema for all of my tables in the Identity DB context, and let's call this schema identity.
And now let's go ahead and generate our database migration. And here's the migration that EF Core has generated for us. And you can see that we are ensuring that the default schema is called identity. But we're also going to add a lot of tables that are needed for the identity system to work. So we have the roles table, the aspnet users table, and you can see that our custom initials column is also present with the max length properly configured. And there are a bunch of other tables that are used by the identity system under the hood and all of them are going to be applied inside of this migration.
Exploring the API and Database Schema
So now let's go ahead and run our API. And I'm going to show you what the identity schema in the database will look like and how we're going to use the identity endpoints for authentication.
I first want to show you the database structure that's generated by the Identity system. And you can see the central table in our database is the aspnet_users table, but we also get a bunch of supporting tables like custom claims and some tables for managing the user's roles. Now you can also see that these tables are created in the identity schema, which is what I configured in my database context. So the Identity system is going to use these tables under the hood to process the user's information and to make authorization possible.
And now let me show you the Swagger UI. What you can see here are the identity endpoints that were added in .NET 8. We configured all of this with just a few lines of code and we can use it to implement authentication in our system. So let's start from the register endpoint. And if I just send this request with the default arguments, we're going to get back a 400 Bad Request which contains a problem details response describing what went wrong. So let me update the email to something like this so [email protected] and then let's send this again, and this time we're going to get some different errors telling us that the password strength isn't too good. So if you fix all of these by specifying a strong password like this one, then you will be able to register the user. So you can see we get back a 200 OK response.
Now if I try to log in the user with these credentials, and let me copy this, I open up the login endpoint and I'm going to send these arguments, the email and the password, and let's execute this. And you can see I'm getting back an internal server error because I didn't configure Bearer authentication, which is the default one. But that's okay because I'm going to specify that I want to use cookies. So let's go ahead and send this request again. And this time we succeed. But if I open up the network tab and execute this request again, so you can see what's going on. If we take a look at the response, you will see that we are getting back a cookie and we're going to use this cookie to authenticate with the API. So now if I go ahead and call any of these endpoints which are authenticated, you will see that I'm getting back a response.
Implementing Bearer Token Authentication
If you want to support token authentication, you can do that by calling AddBearerToken and then you need to specify the authentication schema. So I'm going to access the IdentityConstants and specify the bearer scheme. And now let's call the login endpoint again and see if we get back an access token. Here's the login endpoint and I'm going to send my credentials again, and this time you can see that I'm getting back an access token, but I also get back a refresh token. You can specify the access token value as a bearer token, and you can also use the refresh token to obtain a new access token value. For this, you're going to use the refresh token endpoint and just specify your access token. So if I send this request, we're going to get back a new access token with a new expiration time and a refresh token that we can use to get a new access token when it expires.
Creating and Testing a Protected Endpoint
And I want to show you how to add a protected endpoint to your API. So let's say I have an endpoint with the route of users/me. And what I want to do here is to accept a ClaimsPrincipal instance and my database context, so ApplicationDbContext. And what I'm going to do is to grab the user ID from the claims. The user ID value should be present in the NameIdentifier claim. And I'm expecting this value to be there because I'm going to require authorization for this endpoint to execute. And what I'm going to do is to call my database context, and this is going to be an asynchronous call. And I'm going to say dbContext.Users and I'll say FindAsync and let's pass it the user ID which will be the primary key in the user table in the database. I'm also going to call RequireAuthorization which is going to require of us to be authenticated before we can call this endpoint. So let's test this out.
Here's my new endpoint for fetching the information for the current user and we are fetching this information from the database. Let's log in, and I'm going to use a cookie to authenticate with my API. And let me pass in the email and password. So we get back a 200 OK response. We are authenticated, and now I can just call my endpoint to fetch the information for the current user, and you will see that I'm getting back the information that is stored in the database. So our cookie authentication is working and the user info is persisted in the database. You can just as easily make this work with Bearer tokens, but I wanted to use cookies to make this demo simpler.
If you want to learn more about JSON Web Token authentication then you should watch this video next. Make sure to smash the like and subscribe buttons under this video. And until next time, stay awesome.