Integrate IdentityServer with ASP.NET Core (Part 6 - Users in Db)

In Part - 5 of this series you learned to stored IdentityServer configuration
data such as identity resources, API resources, and clients into a SQL Server
database. However, our sample application still uses TestUser objects to sign
into the system. As you would have guessed, in a more real-world app you would
like to rope in ASP.NET Core Identity to perform membership tasks. In such a
case, instead of using TestUsers you would have IdentityUsers (or its subclass).
Let's see how this can be accomplished in this concluding part of this series.
To keep things simple and focused we won't touch the
IdentityServerDemo.Server project for this example. We will create a new ASP.NET
Core web app that uses ASP.NET Core Identity based authentication. Let's name
this project IdentityServerDemo.Server.AspNetIdentity.

Change the authentication scheme to Individual User Accounts as shown below:

The newly created ASP.NET Core MVC project uses ASP.NET Core Identity and
stores user data in LocalDb database. We will change the database to Northwind
(or whatever database you used earlier). To do so open the appsettings.json file
and change the database connection string as shown below:
"ConnectionStrings": {
"AppDb": "data source=.;initial catalog=
Northwind;Integrated Security=true"
}
You will notice that the newly created project already contains Data >
Migrations folder.

You can also see a custom DbContext class named ApplicationDbContext
automatically created for you.
Now we will create tables required by ASP.NET Core Identity into the
Northwind database. Open Visual Studio developer command prompt and go inside
the newly created project's folder. Then issue the following CLI command :
> dotnet ef database update -context ApplicationDbContext
Once this command completes successfully, you will see the following tables
in the Northwind database :

Next, go to the Startup file and observe the code inside ConfigureServices()
method.
public void ConfigureServices(IServiceCollection services)
{
string connStr = Configuration.GetConnectionString("AppDb");
services.AddDbContext<ApplicationDbContext>
(options => options.UseSqlServer(connStr));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
}
As you can see, the AddDbContext() call registers ApplicationDbContext with
the DI container. Similarly calls to
AddDefaultIdentity() and
AddEntityFrameworkStores() register the required ASP.NET Core Identity
services with the framework. I won't go into the details of these configuration
settings here. You may consider reading the
official documentation.
At this stage the newly created project doesn't contain any IdentityServer
related code but you can go ahead and create couple of user accounts.
To do so, run the newly created application and click on the Register link at
the top.

Note that the default configuration of ASP.NET Core Identity expects an email
as a user name. Supply some user name and password and click on the Register
button to create an account.
Once the accounts are created, click on the Login link at the top and try
signing in to the system by entering a valid user ID and password.

If all goes well you will see a welcome message at the top.

Now that our application is having a few ASP.NET Core Identity users we will
add IdentityServer configuration to the project. For the sake of simplicity we
will use in-memory configuration. But later you can switch to database based
configuration as described in
Part - 5 of this series.
Add the IdentityServer4.AspNetIdentity NuGet packages to the project.

So, copy the ServerConfiguration.cs file from earlier parts of this
series into the newly created project (depending on your project setup you might
need to adjust its namespace). And then modify the ConfigureServices() as shown
below:
public void ConfigureServices(IServiceCollection services)
{
string connStr = Configuration.
GetConnectionString("AppDb");
services.AddDbContext<ApplicationDbContext>
(options => options.UseSqlServer(connStr));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores
<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddIdentityServer()
.AddInMemoryApiResources
(ServerConfiguration.ApiResources)
.AddInMemoryApiScopes
(ServerConfiguration.ApiScopes)
.AddInMemoryIdentityResources
(ServerConfiguration.IdentityResources)
.AddInMemoryClients
(ServerConfiguration.Clients)
.AddDeveloperSigningCredential()
.AddAspNetIdentity<IdentityUser>();
}
Notice that AddIdentityServer() call is placed after the AddIdentity() call.
Also notice that instead of AddTestUsers() call we now use AddAspNetIdentity()
call at the end of the chain. Since we are using the default configuration of
ASP.NET Core Identity, a user is represented by IdentityUser class.
Now wire IdentityServer middleware in the Configure() method.
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env)
{
...
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
...
}
Be sure to insert it between UseRouting() amd UseAuthentication().
You need to make a small change to the Client2 configuration from
ServerConfiguration.cs file. Go to Client2 and set the RequireConsent property
to false.
public static List<Client> Clients
{
get
{
Client client1 = new Client
{
...
};
Client client2 = new Client
{
ClientName = "Client 2",
ClientId = "client2",
...
RequireConsent = false
};
List<Client> clients = new List<Client>();
clients.Add(client1);
clients.Add(client2);
return clients;
}
}
We need this modification because we are using ASP.NET Core Identity in its
simplistic form and don't have consent page ready (the IdentityServer QuickStart
comes with a consent page. So, our earlier projects worked even when
RequireConsent was set to true).
Now, open the IdentityServerDemo.Client project and change the [Authorize]
attribute added on top of HomeController like this :
[Authorize]
public class HomeController : Controller
{
}
Here, we neither use roles nor policies because ASP.NET Core Identity
database doesn't have that information. of course, you can see that data later
to again switch to role or policy based authorization. To keep things simple we
use just the simple authentication here.
You need to make this change in the IdentityServerDemo.WebApi project also.
[Authorize]
public class EmployeesController : ControllerBase
{
[HttpGet]
public List<string> Get()
{
return new List<string>() {
"Nancy Davolio",
"Andrew Fuller",
"Janet Leverling"
};
}
Notice that we have also removed user claims related code that we added in
Part - 4. That is
because default IdentityUser doesn't have properties to hold given_name,
family_name, phone, and address. Of course, you can create a custom user class
that has these properties. Here, we will stick to the bare minimum IdentityUser
class.
This completes the configuration. Now you are ready to run all the three
projects.
First, run the IdentityServerDemo.Server.AspNetIdentity project. Observe that
this project runs on HTTP port 5000 (and HTTPS port 5001). This is the same port
where IdentityServerDemo.Server project used to run. Then run
IdentityServerDemo.WebApi project. Finally, run the IdentityServerDemo.Client
project.
As soon as you run the client application, you will be shown the login page.
Log in with user1@localhost (or whatever email you used during registration
step). You will now see the employee names as shown below:

In the previous part IdentityServer configuration data was stored in a
database and user data was in-memory. In this part user data is stored in a
database and IdentityServer configuration is stored in-memory. But you can
combine what you learned in Part 5 and Part 6 easily so that all the data
(configuration and user) is stored in a database.
That's it for now! Keep coding!!