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.
Intro
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 😉
Two-factor authentication
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:
Pages/Account/Index.cshtml
|
|
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
Pages/Account/ManageTwoFactor.cshtml
|
|
The code-behind for this page is fairly simple, as all we do is make use of the UserManager
and SignInManager
to get the info, as well as forget the browser on a POST request.
Pages/Account/ManageTwoFactor.cshtml.cs
|
|
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.
The out of the box implementation is prepared to use a JavaScript library to present the QR code, but as I’m trying to minimize the JS usage in the authentication service, we’re going to generate the QR code on the server side using SkiaSharp.QrCode.
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).
Base64QrCodeGenerator.cs
|
|
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.
Pages/Account/AddTwoFactorApp.cshtml
|
|
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.
Pages/Account/AddTwoFactorApp.cshtml
|
|
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.
On the Login
page, the redirect to the LoginWithTwoFactor
page was already prepared, but basically the only thing needed is:
Pages/LoginWithTwoFactor.cshtml.cs
|
|
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:
Pages/LoginWithTwoFactor.cshtml.cs
|
|
The LoginWithRecoveryCode
page is mostly the same, but again, a different SignInManager
method is used:
Pages/LoginWithRecoveryCode.cshtml.cs
|
|
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
On PlayBall’s main frontend we’re using npm to manage dependencies. In the auth service, so far we weren’t using anything, as we haven’t needed JavaScript so far, and CSS has been completely forgotten.
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:
Pages/Shared/_Layout.cshtml
|
|
Mostly normal HTML
with a couple of Razor specificities:
- The
environment
tag helper, which allows us to render a specific part of the page depending on the application’s current executing environment. - The
@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:
Pages/_ViewStart.cshtml
|
|
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.
In the 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:
Pages/Shared/_Account.cshtml
|
|
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:
Pages/Account/_ViewStart.cshtml
|
|
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.
In the 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:
Pages/Account/_StatusMessage.cshtml
|
|
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:
|
|
Outro
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
- SkiaSharp.QrCode
- LibMan (Library Manager)
- npm
- Bootstrap
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!