In this episode, we start building the authentication service, using ASP.NET Core Identity and Razor Pages. It will be a standalone application centralizing all the required user authentication logic.
For the walk-through you can check out the next video, but if you prefer a quick read, skip to the written synthesis.
The playlist for the whole series is here.
In this post, we’ll take a look at getting started with ASP.NET Core Identity, which provides the needed bits and pieces to implement authentication for our web applications. We’ll implement this in a standalone authentication service, so it could be used by different client applications (web frontend, mobile app, …).
To implement this application, we’ll use Razor Pages. Since we already took a look at MVC, we can use this opportunity to learn another way of building server-side rendered applications in ASP.NET Core.
Main options to implement authentication
Before we really begin building our authentication service, I wanted to start the post by taking a look at the possible options we have to do it.
We’ll take a look at the following options:
- New web application with Identity pre-configured
- Adding Identity to an application using the scaffolding tool
- Add Identity to an application manually
- Ignore Identity, roll our own
1. New web application with Identity pre-configured
This is the easiest way to get a fully working application with authentication. You’ll probably need to make some adjustments afterwards (e.g. it uses a SQLite database by default) but you’ll be up and running in no time.
To do it from the command-line you can simply use the command:
dotnet new webapp --auth Individual -o WebAppName
auth argument is what says that we want authentication, using individual accounts. Other options are (from the help output):
None - No authentication Individual - Individual authentication IndividualB2C - Individual authentication with Azure AD B2C SingleOrg - Organizational authentication for a single tenant MultiOrg - Organizational authentication for multiple tenants Windows - Windows authentication
If you go down this route, you’ll notice that we have few Identity specific code, mainly a
_ViewStart.cshtml file and some configurations in the
Startup class. That’s because all of the bits of Identity are kept in other packages, including the default UI. If we want to override a specific part of it, we can scaffold it (which ties into the next option).
2. Adding Identity to an application using the scaffolding tool
If we want to implement authentication using Identity in an already existing application (even if existing for the last minute when we created it 😛) or override something from the default implementation, we can use a tool to scaffold it for us.
To start with we must install the tool (if you want to do it from the terminal like I’m doing, if not Visual Studio has got you covered).
Note: All of the following is in the docs, I’m putting it here for quicker reference.
dotnet tool install -g dotnet-aspnet-codegenerator
And we need to add a package to the project.
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
Now we can use scaffold Identity for our project. We have a bunch of options in here as well, for instance, if we run the generator with a
useDefaultUI flag, like
dotnet aspnet-codegenerator identity --useDefaultUI, the end result will be similar to creating the application with pre-configured identity. On the other hand, if we don’t use that flag, the scaffold will include all the Identity UI files, so we can play with it all we want, plus a
DbContext for Identity requirements, some startup configurations and
ScaffoldingReadme.txt which we should check out to make some needed extra configurations.
3. Add Identity to an application manually
Another option, and the one we’re going for this project, is to create an empty application and start building the required authentication pages making use of the classes Identity provides us.
This approach is probably more work than required, as the previous options would end up in about the same result with a lot less work, but since this is more of an academic project, we want to learn as much as possible on how Identity works, so we’ll take the longer path.
To avoid wasting too much time though, we can create another project and scaffold Identity on to it, so we can use it as reference.
4. Ignore Identity, roll our own
We also have the option of ignoring what’s already out there and roll our own. It may make sense in some cases, but I’d say most of the time it doesn’t, particularly in a more complex topic like authentication.
If there is something that’s already tested and proven to work well (and also built by people smarter than me), I would say that using such resources is a better option.
Creating the web application
src folder we create a new empty web application project, by running:
dotnet new web -o CodingMilitia.PlayBall.Auth.Web
Then we can make some changes to the
Startup class, just to prepare it for what we’ll be building, a Razor Pages application (Razor Pages come together with MVC, so right now we won’t see nothing Razor Pages specific).
Now that we have the basics of the application prepared, let’s start the authentication work by creating a
DbContext to handle persistence for us. We could (and probably should) put the database bits in a different project, but to keep it simple for now, we’ll put everything in the web application project.
In a new
Data folder, we create a new class
AuthDbContext. This class inherits from
TUser inherits from
IdentityUser), which provides everything needed by Identity to persist data.
Inheriting from this
IdentityDbContext<TUser> allows us to override certain things that we might want. In this case we’re specifying we want a class
PlayBallUser to represent our user.
If we want the user’s profile to have extra information, besides what’s already present in
IdentityUser, we can add it to a class that inherits from it, in this case,
PlayBallUser. We don’t really need any extra info right now, so
PlayBallUser is just an empty class inheriting from
IdentityUser. In cases such as this, we don’t need to create a new class, we can just use
IdentityUser, but I did it anyway 😛.
Creating migrations is the same as we’ve already seen in episode 011, so I’ll skip that part.
To look at how this context gets associated with Identity, let’s look at the
To prepare our app to use Identity (as well as tell Identity to use the created
AuthDbContext), we must make some changes to the
I probably added too much things that aren’t yet in context, but let’s look at it one thing at a time, starting with
We start with
AddDbContext, registering the database context in the dependency injection container, as we’ve seen in past episodes.
Then we configure Identity services:
AddIdentity<PlayBallUser, IdentityRole>tells Identity we want to use
PlayBallUserto represent the user and
IdentityRole(provided by Identity, like
IdentityUser) to represent the roles, but we won’t really use roles in the application.
- The options allow us to configure a variety of things, in this case we’re just adjusting the password requirements, which have annoying defaults. I prefer to enforce a bigger password than a lot of picky rules.
AddEntityFrameworkStorestells Identity what database context should be used for persistence.
AddDefaultTokenProvidersconfigures Identity to use the default token providers it has out of the box. These are providers for tokens used in things like two-factor authentication, password reset and the like.
After Identity specifics, we have
ConfigureApplicationCookie, which I don’t feel is very well named, as it does more that really just cookie specific stuff. In this case, we’re setting some reference endpoints, so the authentication/authorization process knows where to redirect depending on the situation. We don’t have these pages yet, but when we do, the framework will be able to send the users automatically to the login, logout and access denied pages as needed.
Going into the
Configure method of the
Startup class, we just added the registration of the authentication middleware, by using
UseAuthentication, that’ll take care of checking the authenticated user in all requests, storing its information in the request context (
With some configurations in place, let’s begin creating the pages, starting with the registration page. As mentioned, we’ll be using Razor Pages, but for now we’ll keep it as simple as possible, without worrying about a shared layout or the overall looks of the pages.
Following the usual convention, we create a
Pages folder in the project root, where we’ll put the various pages.
To the new folder, we add a new page named
Register, which creates us two new files,
Register.cshtml.cs. The first one is where we put our page’s markup, the latter will have the server side logic to handle the requests. Although not exactly the same, if we want to map this to MVC, the
*.cshtml would be the view and the
*.cshtml.cs the controller. The
*.cshtml.cs files are also sometimes referred to as code-behind files, as they always go along with the
*.cshtml, adding backing logic. Don’t know if we’re intended to call it that, but it’s something that comes from ASP.NET Web Forms, where we had
Quick note, before continuing with the
Register page, we shouldn’t forget to create a
_ViewImports.cshtml to import the tag helpers, as we’ll need them to build our markup.
We can start by looking at
Register.cshtml file, as it’s rather simple.
At the start of the file, we have a
@page, identifying this as Razor Page and not a regular MVC view, as those are also
*.cshtml files. This makes the page act like a controller’s action by itself, without needing a controller to handle a request and then forward the rendering logic to a view.
The second line in the file indicates what’s the class that implements the action logic for the page. In this case it is
RegisterModel, which is contained in the
The rest of the file is more like what we’re used to in MVC, but with a couple of nuances. The
Model property references the page model (
RegisterModel) defined at the top. As we’ll see when we look at the code-behind file, the
Input we’re referring to is a property of the page model, which is marked with a
BindProperty attribute. This achieves a similar goal to an input model in an MVC controller action.
div with the
asp-validation-summary="All" attribute will show all the validation errors found on the page (only after we click the submit button, as the validation is happening server side). The various
spans with the
asp-validation-for attribute, will show the validation errors for each individual form field.
Now let’s take a look at the page request handling logic, which is in the
Let’s walk-through this wall of code 😛
Starting with the simple bits, the injected dependencies. Take particular notice on
UserManager<PlayBallUser>, as these are some of the most used classes when we’re implementing this authentication logic. The names speak for themselves on what this services do, but we’ll see them in action in no time. The other dependencies are also easy to guess at what their purpose is, a logger and a way to send and email when a user registers, allowing for email confirmation.
Now looking at exposed properties, we have a couple of them, the
ReturnUrl and the previously mentioned
ReturnUrl is passed through requests in the query string, so we store it in a property so we can access it in the page markup (notice its usage in the form tag helper on
Input property is marked with the
BindProperty, which means that it will go through model binding when the page processes a POST request, having its information hydrated by what’s sent by the form.
Following the properties, we see the definition of the
InputModel class. As its really specific to the page, we can just declare inside the
RegisterModel class, but we could also put it somewhere else if we preferred. Its properties decorating attributes are the typical used for validation, also seen in traditional MVC applications. I would say no explaining is needed here, they’re pretty self explanatory.
Finally we reach the request handling logic. By using
OnDelete, a request to the page’s route is matched to the correct handler method given the HTTP method. There are also the async counterparts,
OnGet there’s really not that much going on. We’re just storing the return url in a property so it can be accessed in the HTML creation.
OnPostAsync we do have the registering a new user logic. As an argument, we only get the same return url, but the
Input property has also it’s values initialized with the result of the post submission, as mentioned earlier.
After checking that the model is valid, we make use of the
_userManager to create the new user. If this operation succeeds, we use the
_userManager again for it to generate a token for email confirmation (using
GenerateEmailConfirmationTokenAsync), which we’ll then use in an email sent to the user. It will eventually lead the user to a
ConfirmEmail page, which we have yet to create.
After the email is sent, we can login the user (using the
_signInManager) or maybe redirect to the login page. In this case we’re just signing in.
After this, we can redirect the user to the
ReturnUrl (or somewhere else if this isn’t present).
If the user creation wasn’t successful, we can grab the error information and add it to the
ModelState, so it can be displayed to the user.
Side note: we don’t really need add any anti-forgery validation code, like adding the
ValidateAntiForgeryToken attribute we saw in episode 003, because Razor Pages does it automatically for us.
And another note: the
AllowAnonymous isn’t really needed here, as by default pages allow for anonymous access unless they’re in a folder configured to require the user to be authenticated, which isn’t the case.
Continuing into the login page, we’ll see that it’s more of the same, being the
SignInManager the main Identity piece of interest.
Let’s start with
Simple stuff, very similar to the register page. We have another form with a backing
Input property to handle its fields, plus some validation attributes to show the user what went wrong. Towards the end of the page, we have a couple of links to take the user to recover password and register pages.
Now onto the code-behind.
Like mentioned, this page works in a similar fashion to the register page. The most worthy bits of an extra word are probably the usage of the
SignInManager and temp data.
SignInManager, we’re using it to login the user, making use of the
PasswordSignInAsync method. Besides telling us if the login succeeds or fails, the result has some extra info, like if the account requires two-factor authentication (in which case we should redirect to the page that handles this) or if the account is locked out, meaning the user can’t login at this moment.
Regarding temp data, you might have noticed the
TempData attribute on the
ErrorMessage property. This is can be useful for cases when we want to redirect to another page, but show some extra info. In this case, imagine that something happened in another page that requires the user to login again. That page would set the error message before redirecting to the login page, making the login page display that message. After accessing the temp data, it is removed and not shown again.
By default temp data is stored in cookies, but there are alternatives, as you can see here.
The logout page has really little to look at, but we’ll do it anyway. I’ll skip
Logout.cshtml, has it only shows a
Logout.cshtml.cs, we simply need to use the
SignInManager to logout the user when we get a post request.
Now that we can register, login and logout a user, we can take a look at an accounts settings, where a user can only get after being authenticated.
To keep all the account management related pages, we create a new folder
To make all accesses to pages in this folder be authenticated, we could add the
Authorize attribute to all page model classes, but we can do better than that. Going back to the
Startup class, we can add some Razor Pages conventions to make our lives easier.
By using this
AuthorizeFolder extension method to Razor Pages conventions, we avoid spreading attributes all over the place (and like I mentioned, the
AllowAnonymous attribute would not be required, as the other pages are outside this folder).
There are more things we can configure in Razor Pages using
AddRazorPagesOptions, so you can explore it, but for now this is all we need.
Now to take care of the account page. In the newly created
Account folder, we can add an
Index page, that’ll be the entry point to the account settings.
In here we have another rather simple form. The page shows the current info about the user, in this case just the username and email (which are the same, but could be different if we wanted).
If the email is not yet verified, an option to send a verification email is displayed. If you notice, we’re using a
asp-page-handler attribute, that maps the verification email request to a method in the page model called
OnPostSendVerificationEmailAsync. This is the way to handle requests to a page that don’t map to the usual (in Razor Pages)
OnPost and so on.
Now let’s see what’s going on in
Like previously, the main dependencies to work with are still the
UserManager and the
In the properties department we continue to see the same concepts,
Input to handle the form submissions and temp data to store some messages to present the user.
OnGetAsync, we fetch the current user’s information from the database, passing the
ClaimsPrincipal that represents the user in the current request’s context, which is obtained from the cookies.
After we get all we need (username, email and email verification status) we can present the information to the user.
OnPostAsync, we check if the email as changed, if so, we try to change it, resorting again to the
UserManager. As the username and email are the same, with the current version of ASP.NET Core we need to update them individually, which besides being a pain, may cause issues if the first succeeds and the second fails, as it’s not done in a transaction. There’s an issue open on GitHub regarding this, so hopefully it’ll be resolved soon.
If the email is changed, then we must refresh the user information stored in the cookies, so on the next request the data is consistent with the changes made.
Finally we get to
OnPostSendVerificationEmailAsync which does basically the same as we saw in the register page. Gets some information about the user, including generating a new email verification token, then prepares and sends the email.
This is it for this episode, and it isn’t a small one 😛. In the next episode we’ll continue to look at Identity and building the remaining pages.
I think the main takeaway should be that Identity has us covered for most of the usual requirements when building an application’s authentication process. Everything is very configurable, but if we don’t need all of that, or if we just want to do small modifications, using the defaults plus some scaffolding can save a fair amount of time.
Links in the post:
- ASP.NET Core Razor Pages
- ASP.NET Core Identity
- Scaffold Identity in ASP.NET Core projects
- Session and app state in ASP.NET Core
The source code for this post is here.
Sharing and feedback always appreciated!
Thanks for stopping by, cyaz!