passkeys asp net corePasskeys Implementation

Building a Passkey Login Page with ASP.NET Core

This tutorial shows C# / .NET developers how to integrate passkeys into an ASP.NET Core app. Besides passkeys, simple session management is also implemented.

Blog-Post-Author

Nicolai

Created: October 17, 2023

Updated: October 1, 2024


ChangeUIComponents Icon

We switched from web components to UI components. This blog post will soon be updated. Reach out via Slack if you need help.

Get help

Overview#

1. Introduction

In this blog post, we help C# / .NET developers to implement a sample application with passkey authentication using ASP.NET Core. To make passkeys work, we use Corbado's passkey-first web component that automatically connects to a passkeys backend.

If you want to see the finished code, please have a look at our sample application GitHub repository.

The result looks as follows:

ASP.NET Core: Corbado webcomponent

2. ASP.NET Core passkey project prerequisites

This tutorial assumes basic familiarity with C#, HTML, and the ASP.NET Core framework. Let's dive in!

Debugger Icon

Want to experiment with passkey flows? Try our Passkeys Debugger.

Try for Free

3. Repository structure for the ASP.NET Core passkey project

An ASP.NET Core project contains many files, but the important ones are the following:

├── corbado-demo | ├── api | | ... | | └── Pages | | ├── Index.cshtml # The page which shows info about your profile when logged in | | └── Login.cshtml # The login page | | | └── Properties | └── launchSettings.json # Contains environment variables

4. Set up your Corbado account and project

Visit the Corbado developer panel to sign up and create your account (youll see a passkey sign-up in action here!).

ASP.NET Core: Corbado developer panel Sign Up

In the appearing project wizard, select Web app as type of app and afterwards select No existing users, as were building a new app from scratch. Moreover, providing some details regarding your frontend and backend tech stack as well as the main goal you want to achieve with Corbado helps us to customize and smoothen your developer experience.

Next, we navigate to Settings > General > URLs and set the Application URL, Redirect URL and Relying Party ID to the following values (we will host our app on port 7283):

ASP.NET Core: Corbado developer panel Settings

  • Application URL: Provide the URL where you embedded the web component, here: http://localhost:7283
  • Redirect URL: Provide the URL your app should redirect to after successful authentication and which gets sent a short-term session cookie, here: http://localhost:7283/
  • Relying Party ID: Provide the domain (no protocol, no port and no path) where passkeys should be bound to, here: localhost
Substack Icon

Subscribe to our Passkeys Substack for the latest news, insights and strategies.

Subscribe

5. Create ASP.NET Core app

To initialize our project, we open Visual Studio and use the wizard to create an ASP.NET Core web application:

ASP.NET Core: Visual Studio Setup

Select as seen above and click Continue.

ASP.NET Core: Visual Studio Configure new Web Application

Select the target framework and use No Authentication as authentication option.

ASP.NET Core: Visual Studio Configure new Web Application

Finally, give your project a name and a location and click Create. Visual Studio will automatically open the project for you.

Ben Gould Testimonial

Ben Gould

Head of Engineering

I’ve built hundreds of integrations in my time, including quite a few with identity providers and I’ve never been so impressed with a developer experience as I have been with Corbado.

10,000+ devs trust Corbado & make the Internet safer with passkeys. Got questions? We’ve written 150+ blog posts on passkeys.

Join Passkeys Community

In the pages folder, we can delete all files except for "_ViewImports.cshtml" and "_ViewStart.cshtml":

ASP.NET Core: Pages Folder

Then, we adjust the navigation in Pages/Shared/Layout.cshtml (We already added the login and index routes here):

@{ string projectID = Environment.GetEnvironmentVariable("CORBADO_PROJECT_ID"); string authUrl = "https://" + @projectID + ".frontendapi.corbado.io/auth.js"; string utilityUrl = "https://" + @projectID + ".frontendapi.corbado.io/utility.js"; string cssUrl = "https://" + @projectID + ".frontendapi.corbado.io/auth_cui.css"; } <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - corbado_demo</title> <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <link rel="stylesheet" href="~/corbado_demo.styles.css" asp-append-version="true" /> <link rel="stylesheet" href=@cssUrl asp-append-version="true" /> <script src=@authUrl></script> <script src=@utilityUrl></script> </head> <body> <header> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> <div class="container"> <a class="navbar-brand" asp-area="" asp-page="/Index">corbado_demo</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> <ul class="navbar-nav flex-grow-1"> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a> </li> </ul> <partial name="_LoginPartial" /> </div> </div> </nav> </header> <div class="container"> <main role="main" class="pb-3"> @RenderBody() </main> </div> <footer class="border-top footer text-muted"> <div class="container"> &copy; 2023 - corbado_demo </div> </footer> <script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> @await RenderSectionAsync("Scripts", required: false) </body> </html>

5.1 Configure environment variables

We will need the Corbado project ID in the next steps, so well add it as an environment variable. For this, we edit the /Properties/launchSettings.json file and paste our Corbado project ID:

{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:7887", "sslPort": 44317 } }, "profiles": { "http": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "http://localhost:5286", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "CORBADO_PROJECT_ID": "pro-xxx" }, "dotnetRunMessages": true }, "https": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "https://localhost:7283;http://localhost:5286", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "CORBADO_PROJECT_ID": "pro-xxx" }, "dotnetRunMessages": true }, "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "CORBADO_PROJECT_ID": "pro-xxx" } } } }

5.2 Install NuGet packages

Make sure you have all of the following NuGet packages installed ("Tools > Manage NuGetPackages" inside Visual Studio).

ASP.NET Core: Visual Studio NuGet Packages

6. Create passkey login page

Right click the /Pages folder and select New file. Then select ASP.NET Core > RazorPage (with page model) and click Create.

ASP.NET Core: Visual Studio New File

This will create a Login.cshtml file as well as a Login.cshtml.cs file.

Paste the following code into the Login.cshtml file:

@page @model LoginModel @{ ViewData["Title"] = "Login"; } <div class="text-center"> @if (Model.ShowRequestId) { <p> <strong>Request ID:</strong> <code>@Model.RequestId</code> </p> } <div> <p>Project ID: @Environment.GetEnvironmentVariable("CORBADO_PROJECT_ID")</p> <corbado-auth project-id=@Environment.GetEnvironmentVariable("CORBADO_PROJECT_ID") conditional="yes"> <input name="username" id="corbado-username" required autocomplete="webauthn" /> </corbado-auth> </div> </div>

Afterwards, add this code to the Login.cshtml.cs file:

@page @model IndexModel @{ ViewData["Title"] = "Login"; } <div class="text-center"> @if (@Model.ShowRequestId) { <p> <strong>Request ID:</strong> <code>@Model.RequestId</code> </p> } <div> @if (@Model.userID != null) { <h1 class="display-4">Welcome</h1> <p>Project ID: @Environment.GetEnvironmentVariable("CORBADO_PROJECT_ID")</p> <p>User ID: @Model.userID</p> <p>User Name: @Model.userName</p> <p>User Email: @Model.userEmail</p> <button id="logoutButton">Logout</button> <script inline="javascript"> const projectID = "@Environment.GetEnvironmentVariable("CORBADO_PROJECT_ID")"; const session = new Corbado.Session(projectID); const logoutButton = document.getElementById('logoutButton'); logoutButton.addEventListener('click', function() { session.logout() .then(() => { window.location.replace("/"); }) .catch(err => { console.error(err); }); }); </script> } else { <p>Please login to see your profile</p> } </div> </div>

Our login page contains the Corbado web component, which will handle authentication.

7. Add index page

Like in step 6, we create a Razor page with page model and call it Index which will result in Index.cshtml and Index.cshtml.cs being created. After successful authentication, the Corbado web component redirects the user to the provided Redirect URL (http://localhost:7283/). This is the index page we just created. If someone is logged in, it shows information about the user and provides a button to log out. If no user is available, the page just displays an invitation to login.

For this, we add the following code to Index.cshtml:

using System.Diagnostics; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace corbado_demo.Pages; [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [IgnoreAntiforgeryToken] public class LoginModel : PageModel { public string? RequestId { get; set; } public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); private readonly ILogger<LoginModel> _logger; public LoginModel(ILogger<LoginModel> logger) { _logger = logger; } public void OnGet() { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; } }

7.1 Verify session

Before we can use information embedded in the session, we need to verify that the session is valid. We therefore take the cbo_short_session cookie (the session) and verify its signature using the public key from Corbado. This happens in the Index.cshtml.cs file. We also verify that the issuer is correct (remember to use your own project ID):

using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using Newtonsoft.Json.Linq; using System.Security.Cryptography; using System.Diagnostics; namespace corbado_demo.Pages; [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [IgnoreAntiforgeryToken] public class IndexModel : PageModel { public string? RequestId { get; set; } public string? userID { get; set; } public string? userName { get; set; } public string? userEmail { get; set; } public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); private readonly ILogger<IndexModel> _logger; private readonly IHttpContextAccessor _httpContextAccessor; public IndexModel(ILogger<IndexModel> logger, IHttpContextAccessor httpContextAccessor) { _logger = logger; _httpContextAccessor = httpContextAccessor; } public async Task OnGet() { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; try { string projectID = Environment.GetEnvironmentVariable("CORBADO_PROJECT_ID"); string issuer = $"https://{projectID}.frontendapi.corbado.io"; string jwksUri = $"https://{projectID}.frontendapi.corbado.io/.well-known/jwks"; using (var httpClient = new HttpClient()) { var response = await httpClient.GetStringAsync(jwksUri); var json = JObject.Parse(response); var publicKey = json["keys"][0]["n"].ToString(); var publicKeyBase64 = Base64UrlToBase64(publicKey); var rsaParameters = new RSAParameters { Exponent = Convert.FromBase64String(json["keys"][0]["e"].ToString()), Modulus = Convert.FromBase64String(publicKeyBase64) }; var token = _httpContextAccessor.HttpContext.Request.Cookies["cbo_short_session"]; var tokenHandler = new JwtSecurityTokenHandler(); var validationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = issuer, ValidateAudience = false, ValidateLifetime = true, IssuerSigningKey = new RsaSecurityKey(rsaParameters), ClockSkew = TimeSpan.Zero, RequireSignedTokens = true, RequireExpirationTime = true, ValidateIssuerSigningKey = true }; try { var claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out _); } catch (SecurityTokenValidationException ex) { Console.WriteLine(ex.Message); } } } catch (Exception ex) { Console.WriteLine(ex.Message); } } // Helper function to convert Base64Url to Base64 private string Base64UrlToBase64(string base64Url) { base64Url = base64Url.Replace('-', '+').Replace('_', '/'); while (base64Url.Length % 4 != 0) { base64Url += '='; } return base64Url; } }

7.2 Get data from Corbado session

Finally, we can extract the information stored in the JWT claims:

var claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out _); userID = claimsPrincipal.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value; userName = claimsPrincipal.FindFirst("name")?.Value; userEmail = claimsPrincipal.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")?.Value;

The variables "userID", "userName" and "userEmail" are used in the Index.cshtml template from step 7.

8. Start using passkeys with our ASP.NET Core implementation

To start our application, we head into Visual Studio and run the project (Hit the play button or go to Debug -> Start Debugging).

When visiting http://localhost:7283 you should see the following screen:

ASP.NET Core: Corbado Demo Index page

Clicking on Login will lead you to the Login page:

ASP.NET Core: Corbado Demo Login page

After successful sign up / login, you see the following info on the Index page:

ASP.NET Core: Corbado Demo Welcome page

9. Conclusion

This tutorial showed how easy it is to add passwordless authentication with passkeys to an ASP.NET Core app using Corbado. Besides the passkey-first authentication, Corbado provides simple session management, that we used for a retrieval of basic user data. If you want to read more about how Corbado's session management please check the docs here. If you want to add Corbado to your existing app with existing users, please see our documentation here.

Share this article


LinkedInTwitterFacebook

Table of Contents

Enjoyed this read?

🤝 Join our Passkeys Community

Share passkeys implementation tips and get support to free the world from passwords.

🚀 Subscribe to Substack

Get the latest news, strategies, and insights about passkeys sent straight to your inbox.


We provide UI components, SDKs and guides to help you add passkeys to your app in <1 hour

Start for free