In this episode, we continue exploring ASP.NET Core Identity and Razor Pages as we continue development of our authentication service.
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 wrap up the login and registration process we started in the last episode, using ASP.NET Core Identity and Razor Pages.
Changes since the last post
I’ll skip through some of the stuff I did since the previous post, as not all of it seems too interesting, but in summary:
- Made adjustments to the previously created pages, in preparation for adding Bootstrap.
- Added some missing pages, like email confirmation, password change and reset.
If you have any question regarding these, feel free to reach out 😉
To implement two-factor authentication, we’ll use Time-based One-time Password algorithm or regularly abbreviated to TOTP.
Like other parts of the authentication process we’ve implemented so far, two-factor authentication (I’ll abbreviate to 2fa from now on) is not very hard to get working because ASP.NET Core Identity provides everything we need to do it. It does however need a good amount of pages to get the complete feature set working, so let’s take a quick look at the main steps needed:
- A way for the user to enable and configure 2fa
- Present a QR code for the user to setup a device
- Generate and show the user a list of recovery codes, in case anything happens to the configured device
- Disable 2fa, also resetting configured devices
- Add a step to the login process to ask for the 2fa code, or a recovery code as fallback
I won’t go into the details of all these steps, as I think most of them shouldn’t be too hard to get by looking at the code, so I’ll quickly go through only some of them.
As we begin looking at some code, remember most of it comes out of the box when we scaffold all the bits from Identity, as we saw in the previous episode. Some parts of what I’ll show are exactly what comes out of the box, others I adapted a bit to be more to my liking.
Managing two-factor authentication
In the account home page, we need a way to link to the 2fa management page, so by the end of
Index.cshtml we add the following:
Then we need an home page for 2fa management, which in this case is
ManageTwoFactor.cshtml. In this page we just show some options depending on the current state of the user’s 2fa configuration.
If the user already has two factor enabled, depending on the recovery codes usage, we might recommend the generation of a new set of codes.
Besides this, it’s here we present the user with the options to:
- Disable 2fa (if enabled)
- Forget the current browser and ask for the code on next login
- Add authenticator devices or reset existing ones
The code-behind for this page is fairly simple, as all we do is make use of the
SignInManager to get the info, as well as forget the browser on a POST request.
Setting up a new authenticator device
When setting up a new device, we head on to
AddTwoFactorApp.cshtml. In here we present a QR code (and just the key directly if the user prefers/needs to type it) for the user to scan on the authenticator app. After scanning, the user should introduce the code to proceed with the configuration process.
Going with the server side generation, I thought of a couple of options to deliver the QR code to the frontend:
- Have one endpoint receiving the key and generating the image as output
- Generate and encode the image in base 64, then embed it directly in the page
The first option is probably cleaner, as it would work as a regular static image from the page’s perspective, but being a GET request, we would end up with the shared key in the server logs. If I can avoid leaking secrets, I will, so a base 64 encoded image it is! 🙂
To implement the QR code generation, we create a new class (with matching interface) aptly named
Base64QrCodeGenerator (and not forgetting to register it in the DI container afterwards).
Honestly, didn’t spend that much time one this code, it can surely be optimized. Anyway, we’ve got ourselves a very basic QR code generator, that gets an
Uri and returns the base 64 encoded image.
I won’t paste the
AddTwoFactorApp.cshtml code here, as it just shows the QR code and has an input for the user to insert the code from her device. On the code behind though, we can take a look at some bits.
On the GET request, we make use of the
UserManager to get the shared two-factor key, then format the key for presentation and create the Qr code for it.
On the POST request, we verify the inserted code, again using the
UserManager. If the code is valid, we set 2fa as enabled and if there aren’t any recovery codes generated yet, we generate the codes and put them in a temporary data property, then redirecting the user to the
ShowRecoveryCodes page to see them.
Log in with two-factor authentication
With two-factor authentication setup, now when the user logs in we must make sure he inserts the code from one of his devices during the process.
Login page, the redirect to the
LoginWithTwoFactor page was already prepared, but basically the only thing needed is:
LoginWithTwoFactor is a simple page with a form for the user to introduce her device’s code. On the code-behind side, the logic is similar to what we saw in
Login.cshtml.cs, being the major difference the
SignInManager method we use to valid the data:
LoginWithRecoveryCode page is mostly the same, but again, a different
SignInManager method is used:
With this I think we’ve seen the main parts of the two-factor authentication implementation. Again, if looking at the code on GitHub something doesn’t add up, poke me!
Adding client side libraries with LibMan
Now we’ll change this a bit, and use Bootstrap to try and make our pages a little less hideous. I know Bootstrap isn’t the de facto standard it was, but I just want the pages to be a little more usable and don’t spend too much time with CSS, as it’s far from my best skill 😛.
We could import Bootstrap directly from a CDN, but that way we couldn’t see LibMan in action.
LibMan (short for Library Manager) is a simple .NET command line utility we can use to manage frontend libraries.
We can install LibMan with the following command:
Then in our web project’s folder, we can run
libman init to get it initialized. It will ask us for the the default provider, which if we don’t want to specify will be cdnjs. The result is then a
json file with some metadata:
If we want to have the libraries go to a specific folder by default, we can do
libman init --default-destination wwwroot/lib, which gives us:
Now we want to have Bootstrap installed, so we do
libman install twitter-bootstrap and get:
We can also specify the provider and target path when installing a library, but as we have set the defaults, we don’t need to.
In the next section, creating a base layout for the pages, we can include a link to the Bootstrap assets we just installed.
LibMan has more options than what I’m showing here, but this was all I needed so far (check the docs for more info). Not that it has a lot of options though, as it’s supposed to be really simple. If we need more complexity, there is no shortage of alternatives.
Adding a base layout to the pages
To add a base layout to all the pages, we can start by creating a
Shared folder as a child of
Pages. In there we create a new
_Layout.cshtml file (it’s part of the templates and all). In the new file, we can add something like:
HTML with a couple of Razor specificities:
environmenttag helper, which allows us to render a specific part of the page depending on the application’s current executing environment.
@RenderBody()method call, which we put in the place we want the contents of the requested page to be rendered.
Now to use the layout, we could go to every page and configure it in the header, but that would be a lot of work and we would eventually forget something. Instead, we can create a
_ViewStart.cshtml file in the root of the
Pages folder, with the following contents:
Now this file will be executed by all the other views in that folder (and descendants), making them all use the base layout we created.
Account sub-folder however, we’ll want a slightly different layout, to allow for the user to logout. To achieve this we can follow a really similar approach. Create another
Shared folder, this time as a descendant of
Account, and in there create another layout file (this time I called it
_Account.cshtml). In this new file, we can add something like:
This makes this layout inherit from the original one, while adding a logout button. In the root of the
Account folder we can now create another
_ViewStart.cshtml file with:
Adding a partial view for the status message
To wrap up this post, let’s take a look at creating a partial view.
Remember that status message we put at the top of most of the pages, and in the last episode I just put it inside a
div? Well, let’s put it into a partial view and give it a better UI.
Pages/Account folder, we can add a new partial view (there is a file template for it) called
_StatusMessage.cshtml. In there we have the following:
The model is a single
string, as it’s all we need for now. Then if we have a filled model (if not we won’t render anything) we show the message, decorated with some Bootstrap classes, plus a button to dismiss the message.
Now on every page where we had the
div with the message, we can replace with the following, in order to use the partial view:
This is it for this post on ASP.NET Core Identity and Razor Pages. We took a look at some of the most interesting bits of implementing the authentication flow (from my perspective of course). Again, don’t forget that ASP.NET Core Identity already comes with most of this prepared out of the box, so only go into this much trouble if you need to customize it.
Links in the post:
- ASP.NET Core Razor Pages
- ASP.NET Core Identity
- Time-based One-time Password algorithm
- LibMan (Library Manager)
The source code for this post is here.
On a closing note, when I recorded the video not so much, but after writing the post, this feels a bit all over the place, too many topics condensed into one video/post. I’ll try to come up with a better organization strategy going forward 🙂
Given I’m implementing a full application, I guess it’s normal to have several topics condensed in a single episode, but I’m not completely sure the reading experience is great, so let me know if you have suggestions.
Sharing and feedback always appreciated!
Thanks for stopping by, cyaz!