Securing the Administration Features
Authentication and authorization are provided by the ASP.NET Core Identity system, which integrates neatly into the ASP.NET Core platform and the individual application frameworks. In the sections that follow, I will create a basic security setup that allows one user, called Admin, to authenticate and access the administration features in the application. ASP.NET Core Identity provides many more features for authenticating users and authorizing access to application features and data, and you can find more information in Chapters 37 and 38, where I show you how to create and manage user accounts and how to perform authorization using roles. But, as I noted previously, ASP.NET Core Identity is a large framework in its own right, and I cover only the basic features in this book.
My goal in this chapter is just to get enough functionality in place to prevent customers from being able to access the sensitive parts of the SportsStore application and, in doing so, give you a flavor of how authentication and authorization fit into an ASP.NET Core application.
You can download the example project for this chapter—and for all the other chapters in this book—from https://github.com/apress/pro-asp.net-core-3. See Chapter 1 for how to get help if you have problems running the examples.
Creating the Identity Database
The ASP.NET Identity system is endlessly configurable and extensible and supports lots of options for how its user data is stored. I am going to use the most common, which is to store the data using Microsoft SQL Server accessed using Entity Framework Core.
Installing the Identity Package for Entity Framework Core
Installing the Entity Framework Core Package
Creating the Context Class
The Contents of the AppIdentityDbContext.cs File in the SportsStore/Models Folder
The AppIdentityDbContext class is derived from IdentityDbContext, which provides Identity-specific features for Entity Framework Core. For the type parameter, I used the IdentityUser class, which is the built-in class used to represent users.
Defining the Connection String
Defining a Connection String in the appsettings.json File in the SportsStore Folder
Remember that the connection string has to be defined in a single unbroken line in the appsettings.json file and is shown across multiple lines in the listing only because of the fixed width of a book page. The addition in the listing defines a connection string called IdentityConnection that specifies a LocalDB database called Identity.
Configuring the Application
Configuring Identity in the Startup.cs File in the SportsStore Folder
In the ConfigureServices method, I extended the Entity Framework Core configuration to register the context class and used the AddIdentity method to set up the Identity services using the built-in classes to represent users and roles.
In the Configure method, I called the UseAuthentication and UseAuthorization methods to set up the middleware components that implement the security policy. These methods must appear between the UseRouting and UseEndpoints methods.
Creating and Applying the Database Migration
Creating the Identity Migration
The important difference from previous database commands is that I have used the -context argument to specify the name of the context class associated with the database that I want to work with, which is AppIdentityDbContext. When you have multiple databases in the application, it is important to ensure that you are working with the right context class.
Applying the Identity Migration
The result is a new LocalDB database called Identity that you can inspect using the Visual Studio SQL Server Object Explorer.
Defining the Seed Data
The Contents of the IdentitySeedData.cs File in the SportsStore/Models Folder
This code ensures the database is created and up-to-date and uses the UserManager<T> class, which is provided as a service by ASP.NET Core Identity for managing users, as described in Chapter 38. The database is searched for the Admin user account, which is created—with a password of Secret123$—if it is not present. Do not change the hard-coded password in this example because Identity has a validation policy that requires passwords to contain a number and range of characters. See Chapter 38 for details of how to change the validation settings.
Hard-coding the details of an administrator account is often required so that you can log into an application once it has been deployed and start administering it. When you do this, you must remember to change the password for the account you have created. See Chapter 38 for details of how to change passwords using Identity. See Chapter 15 for how to keep sensitive data, such as default passwords, out of source code control.
Seeding the Identity Database in the Startup.cs File in the SportsStore Folder
Restart the application, and the database will be re-created and populated with seed data.
Adding a Conventional Administration Feature
The Contents of the IdentityUsers.cshtml File in the SportsStore/Pages/Admin Folder
Applying a Basic Authorization Policy
Now that I have configured ASP.NET Core Identity, I can apply an authorization policy to the parts of the application that I want to protect. I am going to use the most basic authorization policy possible, which is to allow access to any authenticated user. Although this can be a useful policy in real applications as well, there are also options for creating finer-grained authorization controls, as described in Chapters 37 and 38, but since the SportsStore application has only one user, distinguishing between anonymous and authenticated requests is sufficient.
Restricting Access in the IdentityUsers.cshtml File in the SportsStore/Pages/Admin Folder
Applying Authorization in the Index.cshtml File in the SportsStore/Pages/Admin Folder
Since this Razor Page has been configured with a page model class, I can apply the attribute with an @attribute expression.
Creating the Account Controller and Views
The Contents of the LoginModel.cs File in the SportsStore/Models/ViewModels Folder
The Contents of the AccountController.cs File in the SportsStore/Controllers Folder
When the user is redirected to the /Account/Login URL, the GET version of the Login action method renders the default view for the page, providing a view model object that includes the URL that the browser should be redirected to if the authentication request is successful.
Authentication credentials are submitted to the POST version of the Login method, which uses the UserManager<IdentityUser> and SignInManager<IdentityUser> services that have been received through the controller’s constructor to authenticate the user and log them into the system. I explain how these classes work in Chapters 37 and 38, but for now, it is enough to know that if there is an authentication failure, then I create a model validation error and render the default view; however, if authentication is successful, then I redirect the user to the URL that they want to access before they are prompted for their credentials.
In general, using client-side data validation is a good idea. It offloads some of the work from your server and gives users immediate feedback about the data they are providing. However, you should not be tempted to perform authentication at the client, as this would typically involve sending valid credentials to the client so they can be used to check the username and password that the user has entered, or at least trusting the client’s report of whether they have successfully authenticated. Authentication should always be done at the server.
The Contents of the Login.cshtml File in the SportsStore/Views/Account Folder
Adding a Logout Button in the AdminLayout.razor File in the SportsStore/Pages/Admin Folder
Testing the Security Policy
Everything is in place, and you can test the security policy by restarting ASP.NET Core and requesting http://localhost:5000/admin or http://localhost:5000/admin/identityusers.
Preparing ASP.NET Core for Deployment
In this section, I will prepare SportsStore and create a container that can be deployed into production. There is a wide range of deployment models available for ASP.NET Core applications, but I have picked Docker containers because they can be run on most hosting platforms or be deployed into a private data center. This is not a complete guide to deployment, but it will give you a sense of the process to prepare an application.
Configuring Error Handling
The Contents of the Error.cshtml File in the Pages Folder
This kind of error page is the last resort, and it is best to keep it as simple as possible and not to rely on shared views, view components, or other rich features. In this case, I have disabled shared layouts and defined a simple HTML document that explains that there has been an error, without providing any information about what has happened.
Configuring Error Handling in the Startup.cs File in the SportsStore Folder
As I explain in Chapter 12, the IWebHostEnvironment parameter defined by the Configure method describes the environment in which the application is running. The changes mean that the UseExceptionHandler method is called when the application is in production, but the developer-friendly error pages are used otherwise.
Creating the Production Configuration Settings
The JSON configuration files that are used to define settings such as connection strings can be created so they apply only when the application is in a specific environment, such as development, staging, or production. The template I used to create the SportsStore project in Chapter 7 created the appsettings.json and appsettings.Development.json files, which are intended to be the default settings that are overridden with those that are specific for development. I am going to take the reverse approach for this chapter and define a file that contains just those settings that are specific to production. Add a JSON File named appsettings.Production.json to the SportsStore folder with the content shown in Listing 11-18.
Do not use these connection strings in real projects. You must correctly describe the connection to your production database, which is unlikely to be the same as the ones in the listing.
The Contents of the appsettings.Production.json File in the SportsStore Folder
These connection strings, each of which is defined on a single line, describe connections to SQL Server running on sqlserver, which is another Docker container running SQL Server.
Creating the Docker Image
In the sections that follow, I configure and create the Docker image for the application that can be deployed into a container environment such as Microsoft Azure or Amazon Web Services. Bear in mind that containers are only one style of deployment and there are many others available if this approach does not suit you.
Bear in mind that I am going to connect to a database running on the development machine, which is not how most real applications are configured. Be sure to configure the database connection strings and the container networking settings to match your production environment.
Installing Docker Desktop
Go to Docker.com and download and install the Docker Desktop package. Follow the installation process, reboot your Windows machine, and run the command shown in Listing 11-19 to check that Docker has been installed and is in your path. (The Docker installation process seems to change often, which is why I have not been more specific about the process.)
You will have to create an account on Docker.com to download the installer.
Checking the Docker Desktop Installation
Creating the Docker Configuration Files
The Contents of the Dockerfile File in the SportsStore Folder
The Contents of the docker-compose.yml File in the SportsStore Folder
The YML files are especially sensitive to formatting and indentation, and it is important to create this file exactly as shown. If you have problems, then use the docker-compose.yml file from the GitHub repository for this book, https://github.com/apress/pro-asp.net-core-3.
Publishing and Imaging the Application
Preparing the Application
Performing the Docker Build
Click the Allow button, return to the PowerShell prompt, use Control+C to terminate the Docker containers, and run the command in Listing 11-23 again.
Running the Containerized Application
Starting the Containers
Summary
In this and previous chapters, I demonstrated how the ASP.NET Core can be used to create a realistic e-commerce application. This extended example introduced many key features: controllers, action methods, views, Razor Pages, Blazor, routing, validation, authentication, and more. You also saw how some of the key technologies related to how ASP.NET Core can be used. These included the Entity Framework Core, ASP.NET Core Identity, and unit testing. And that’s the end of the SportsStore application. In the next part of the book, I start to dig into the details of ASP.NET Core.