ASP.NET Core 10 Authentication Guide — JWT, Passkeys, Cookies & More

ASP.NET Core 10 Security Guide 🔥 2026

ASP.NET Core 10 Authentication Guide — JWT, Passkeys, Cookies & More

A complete, production-ready guide covering every authentication method in ASP.NET Core 10 — with real C# code examples you can use today.

AS

Adnan Shaukat

May 17, 2026  ·  14 min read  ·  Backend Developer

Authentication is the most critical part of any backend application. Get it wrong and your users' data is at risk. Get it right and your app is production-ready from day one.

ASP.NET Core 10 brings some of the biggest authentication upgrades in years — built-in Passkey (WebAuthn) support, improved cookie authentication for API endpoints, new Identity metrics, and cleaner JWT setup. In this guide I will walk you through every authentication method with working C# code so you can implement them in your own projects today.

01 — Fundamentals Authentication vs Authorization

Before writing a single line of code, every backend developer must understand the difference between these two terms. They are not the same thing and confusing them causes serious security holes.

  • Authentication — verifies who you are. "Is this person really Adnan?" It checks identity using credentials like passwords, tokens, or biometrics.
  • Authorization — verifies what you are allowed to do. "Can Adnan delete posts?" It checks permissions after identity is confirmed.

In ASP.NET Core 10 the flow is always: Authentication first → Authorization second. You cannot authorize someone whose identity you have not confirmed.

Real-world example

Your passport proves who you are (authentication). The visa stamp in your passport says which countries you can enter (authorization). Same pattern applies in your API.

02 — Most Common JWT Bearer Authentication

JWT (JSON Web Token) is the most widely used authentication method for REST APIs and microservices. When a user logs in, your server issues a signed JWT token. The client sends this token in every subsequent request, and your API validates it without touching the database.

Step 1 — Install the NuGet package

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Step 2 — Configure JWT in Program.cs

C#
// Program.cs — ASP.NET Core 10 var builder = WebApplication.CreateBuilder(args); // Add JWT Authentication builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]!)) }; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseAuthentication(); // Always before UseAuthorization app.UseAuthorization(); app.Run();

Step 3 — Generate a JWT token on login

C#
// AuthService.cs — generate JWT token public class AuthService { private readonly IConfiguration _config; public AuthService(IConfiguration config) => _config = config; public string GenerateToken(string userId, string email, string role) { var claims = new[] { new Claim(ClaimTypes.NameIdentifier, userId), new Claim(ClaimTypes.Email, email), new Claim(ClaimTypes.Role, role), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; var key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(_config["Jwt:SecretKey"]!)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _config["Jwt:Issuer"], audience: _config["Jwt:Audience"], claims: claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } } // Minimal API login endpoint app.MapPost("/api/auth/login", (LoginRequest req, AuthService auth) => { // Validate user credentials against your database here var token = auth.GenerateToken("user-123", req.Email, "User"); return Results.Ok(new { token }); }); // Protected endpoint app.MapGet("/api/profile", (ClaimsPrincipal user) => { var email = user.FindFirst(ClaimTypes.Email)?.Value; return Results.Ok(new { email }); }).RequireAuthorization();

appsettings.json — JWT configuration

JSON
{ "Jwt": { "Issuer": "https://codewithadnanshaukat.blogspot.com", "Audience": "YourAppUsers", "SecretKey": "YourSuperSecretKey_AtLeast32CharsLong!" } }

⚠ Security warning

Never store your JWT SecretKey in appsettings.json in production. Use environment variables or Azure Key Vault. The key should be at least 32 characters long and randomly generated.

Cookie authentication is used for traditional web applications with server-rendered pages. After login, the server issues an encrypted cookie that the browser sends automatically with every request.

C#
// Program.cs — Cookie Authentication setup builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/account/login"; options.LogoutPath = "/account/logout"; options.AccessDeniedPath = "/account/access-denied"; options.ExpireTimeSpan = TimeSpan.FromHours(8); options.SlidingExpiration = true; options.Cookie.HttpOnly = true; // Prevents JavaScript access options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // HTTPS only options.Cookie.SameSite = SameSiteMode.Strict; }); // Sign in a user after credential validation app.MapPost("/account/login", async (LoginRequest req, HttpContext ctx) => { // Validate credentials against your database here... var claims = new List<Claim> { new Claim(ClaimTypes.Name, req.Email), new Claim(ClaimTypes.Role, "User") }; var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal); return Results.Redirect("/"); });

The .NET 10 Breaking Change — API endpoints now return 401/403

This is an important change that will affect you if you are mixing cookie auth with API endpoints. In previous versions, unauthenticated requests to API endpoints got a 302 Redirect to the login page — even from API clients. This was a well-known annoyance.

In .NET 10, when IApiEndpointMetadata is present on an endpoint, the cookie authentication handler now correctly returns:

  • 401 Unauthorized — for unauthenticated requests
  • 403 Forbidden — for authenticated but unauthorized requests

This is correct REST API behaviour. However if you want to keep the old redirect behaviour, you can override it:

C#
// .NET 10 — override to keep redirect behaviour (optional) .AddCookie(options => { options.Events.OnRedirectToLogin = context => { context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; }; options.Events.OnRedirectToAccessDenied = context => { context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; }; });

04 — Identity ASP.NET Core Identity — Production Setup

ASP.NET Core Identity is Microsoft's full membership system — it handles user registration, login, password hashing, email confirmation, lockout, 2FA, and more. It is the right choice for any app that manages its own users.

Install packages

dotnet new webapp -n MySecureApp
cd MySecureApp
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools

Setup Identity with EF Core

C#
// AppUser.cs — extend IdentityUser with custom fields public class AppUser : IdentityUser { public string FullName { get; set; } = string.Empty; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; } // AppDbContext.cs public class AppDbContext : IdentityDbContext<AppUser> { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } } // Program.cs — wire everything up builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("Default"))); builder.Services.AddIdentity<AppUser, IdentityRole>(options => { // Password policy options.Password.RequiredLength = 8; options.Password.RequireUppercase = true; options.Password.RequireDigit = true; options.Password.RequireNonAlphanumeric = true; // Lockout policy — important for brute force protection options.Lockout.MaxFailedAccessAttempts = 5; options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15); options.Lockout.AllowedForNewUsers = true; // Require confirmed email before login options.SignIn.RequireConfirmedEmail = true; }) .AddEntityFrameworkStores<AppDbContext>() .AddDefaultTokenProviders();

05 — New in .NET 10 Passkeys (WebAuthn) — Passwordless Authentication

This is the most exciting authentication feature in .NET 10. Passkeys let users sign in using their device's biometrics (fingerprint, Face ID) or a hardware security key — no password required. They are built on the WebAuthn and FIDO2 standards and are completely phishing-resistant.

Why passkeys matter

Passkeys cannot be phished, stolen in data breaches (only public keys are stored), or guessed. A real developer who built passkey auth for a healthcare portal reported zero forgotten password support tickets after switching. That alone saves hours of developer time every week.

Configure passkeys in .NET 10

C#
// Program.cs — configure passkey options in .NET 10 builder.Services.Configure<IdentityPasskeyOptions>(options => { // Your domain (Relying Party ID) — must match your website domain options.ServerDomain = "codewithadnanshaukat.blogspot.com"; // How long the user has to complete the passkey ceremony options.AuthenticatorTimeout = TimeSpan.FromMinutes(3); // Size of the cryptographic challenge in bytes options.ChallengeSize = 64; }); // The Blazor Web App template includes passkey UI out of the box. // For custom apps, you handle two endpoints: // 1. Registration — generate challenge, browser calls navigator.credentials.create() app.MapPost("/passkey/register/begin", async ( HttpContext ctx, UserManager<AppUser> userManager) => { var user = await userManager.GetUserAsync(ctx.User); var options = await userManager.CreatePasskeyCreationOptionsAsync(user!); return Results.Ok(options); }).RequireAuthorization(); // 2. Login — verify the passkey signature from the device app.MapPost("/passkey/login", async ( PasskeyAssertion assertion, SignInManager<AppUser> signInManager) => { var result = await signInManager.PasskeySignInAsync(assertion); return result.Succeeded ? Results.Ok() : Results.Unauthorized(); });

Current limitation

In .NET 10, the Blazor Web App template still requires users to set a password during initial registration before adding a passkey. Passkeys are an additional login method, not a complete password replacement yet in the default templates.

06 — 2FA Two-Factor Authentication (TOTP)

Two-factor authentication adds a second verification step after the password. In ASP.NET Core Identity, TOTP (Time-based One-Time Password) is the most common 2FA method — it works with apps like Google Authenticator and Microsoft Authenticator.

C#
// Enable 2FA — generate a TOTP authenticator key for the user app.MapGet("/account/enable-2fa", async ( HttpContext ctx, UserManager<AppUser> userManager) => { var user = await userManager.GetUserAsync(ctx.User); await userManager.ResetAuthenticatorKeyAsync(user!); var key = await userManager.GetAuthenticatorKeyAsync(user!); // Show this key to the user as a QR code for their authenticator app return Results.Ok(new { AuthenticatorKey = key }); }).RequireAuthorization(); // Verify 2FA code and complete login app.MapPost("/account/verify-2fa", async ( TwoFactorRequest req, SignInManager<AppUser> signInManager) => { var result = await signInManager.TwoFactorAuthenticatorSignInAsync( req.Code, isPersistent: false, rememberClient: false ); return result.Succeeded ? Results.Ok(new { message = "Login successful" }) : Results.BadRequest(new { message = "Invalid code" }); }); public record TwoFactorRequest(string Code);

07 — Authorization Role-Based and Claims-Based Authorization

Once authentication is confirmed, authorization decides what the user can do. ASP.NET Core 10 supports three authorization approaches — roles, claims, and policies.

C#
// Program.cs — define authorization policies builder.Services.AddAuthorization(options => { // Role-based policy options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); // Claims-based policy options.AddPolicy("CanPublish", policy => policy.RequireClaim("Permission", "Publish")); // Combined policy — must be authenticated AND have a role options.AddPolicy("SeniorDev", policy => policy.RequireAuthenticatedUser() .RequireRole("Developer") .RequireClaim("YearsExperience", "5")); }); // Blog API example — different access levels // Anyone can read posts app.MapGet("/api/posts", () => Results.Ok("All posts")); // Only logged-in users can comment app.MapPost("/api/posts/{id}/comments", (CommentRequest req) => Results.Ok("Comment added")) .RequireAuthorization(); // Only admins can create or delete posts app.MapPost("/api/posts", (PostRequest req) => Results.Ok("Post created")) .RequireAuthorization("AdminOnly"); app.MapDelete("/api/posts/{id}", (int id) => Results.Ok("Post deleted")) .RequireAuthorization("AdminOnly");

08 — New in .NET 10 Identity Metrics & Observability

ASP.NET Core 10 adds built-in authentication and authorization metrics — something developers have been asking for since Identity was first released. You can now monitor these events out of the box:

  • User management — new user registrations, password changes, role assignments
  • Login/session tracking — login attempts, successful sign-ins, sign-outs, 2FA usage
  • Request duration — authenticated request duration visible in the Aspire dashboard
  • Auth/authz events — authorization successes and failures per endpoint
C#
// .NET 10 — metrics are available automatically under this meter // Microsoft.AspNetCore.Identity // Connect to .NET Aspire or Prometheus to see: // - aspnetcore.identity.user_management.count // - aspnetcore.identity.login.count // - aspnetcore.identity.login.duration // - aspnetcore.authorization.requests (succeeded/failed) // To expose metrics via OpenTelemetry in your app: builder.Services.AddOpenTelemetry() .WithMetrics(metrics => { metrics .AddAspNetCoreInstrumentation() .AddMeter("Microsoft.AspNetCore.Identity") .AddPrometheusExporter(); // or use Aspire dashboard });

09 — Checklist Security Best Practices

Before shipping any authentication system to production, go through this checklist:

Store JWT secret keys in environment variables — never in source code or appsettings.json

Enable account lockout — protects against brute-force password attacks

Use HTTPS only — set CookieSecurePolicy.Always and enforce HTTPS in Kestrel

Set short JWT expiry — 15–60 minutes, use refresh tokens for longer sessions

Enable 2FA for admin accounts — at minimum, all admin users must use TOTP

Set HttpOnly and SameSite on cookies — prevents XSS and CSRF attacks

Require email confirmation — set RequireConfirmedEmail = true in Identity options

Monitor auth metrics — use .NET 10's built-in Identity metrics to detect anomalies

Consider passkeys — for any new user-facing app, offer WebAuthn as a login option in .NET 10

Final Thoughts

.NET 10 is the best version of ASP.NET Core for building secure applications. Passkeys, proper API status codes, built-in metrics, and a rock-solid Identity system give you everything you need without third-party dependencies. Start with JWT for your APIs, add Identity for user management, and offer passkeys to give your users the best login experience available today.

ASP.NET Core 10 Authentication JWT Passkeys WebAuthn .NET 10 C# Security Backend
AS

Written by

Adnan Shaukat

Backend Developer with 5+ years in .NET, Python, and REST APIs. Writing daily at codewithadnanshaukat.blogspot.com

Comments

Popular posts from this blog

What is Agentic AI? A Complete Developer Guide for 2026

CQRS Pattern in .NET 10 with MediatR — Complete Step-by-Step Tutorial

EF Core 10 Performance Tips — 12 Ways to Make Your Queries Blazing Fast