Authentication using ASP.NET Core Identity (.NET 10)

ZZZ Projects' EF Core Extensions Struggling with slow EF Core operations? Boost performance like never before. Experience up to 14× faster Bulk Insert, Update, Delete, and Merge - and cut your save time by as much as 94%. Learn more →
Introduction
If you've ever built a .NET Web API that needed authentication, you know the drill: create an AuthController, wire up UserManager, write register/login endpoints from scratch...
It's a lot of boilerplate.
Not anymore.
Starting with .NET 8 and improved in .NET 10, ASP.NET Core ships with Identity API Endpoints - a set of ready-made endpoints for registration, login, token refresh, and more, all backed by ASP.NET Core Identity and Bearer tokens.
In this article, I'll walk you through setting up authentication from zero in a .NET 10 Minimal API project:
- Installing and configuring Identity + EF Core
- Running database migrations
- Using the built-in Identity endpoints
- Protecting routes with
RequireAuthorization() - Authenticating with Bearer tokens
What are Identity API Endpoints?
MapIdentityApi<TUser>() is a single method call that maps all the authentication endpoints you need:
| Endpoint | Method | Description |
|---|---|---|
/register | POST | Register a new user |
/login | POST | Login and receive a Bearer token |
/refresh | POST | Refresh an expired token |
/confirmEmail | GET | Confirm email address |
/manage/info | GET / POST | Get or update user info |
No controllers. Just one line.
Project Setup
1. Create the Project
dotnet new webapi -n IdentityApiDemo --use-minimal-apis
cd IdentityApiDemo
Make sure you're targeting .NET 10 in your .csproj:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
2. Install Required Packages
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
Or if you prefer SQLite for local development:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
The Data Model
AppDbContext - Wiring Up Identity
public class AppDbContext : IdentityDbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options) { }
}
Key point: Inherit from IdentityDbContext instead of plain DbContext. This automatically includes all the Identity tables (AspNetUsers, AspNetRoles, AspNetUserTokens, etc.) - no extra configuration needed.
Program.cs Setup
Here's what's important to set up in the DI container:
using IdentityApiDemo.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// 1. Add OpenAPI
builder.Services.AddOpenApi();
// 2. Configure DbContext
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
// 3. Configure Identity with Bearer token authentication
builder.Services
.AddIdentityApiEndpoints<IdentityUser>()
.AddEntityFrameworkStores<AppDbContext>();
// 4. Add Authorization
builder.Services.AddAuthorization();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
app.UseHttpsRedirection();
// 5. Map the built-in Identity endpoints
app.MapIdentityApi<IdentityUser>();
// 6. Your protected endpoints
app.MapGet("/me", (ClaimsPrincipal user) =>
{
return Results.Ok(new
{
Email = user.FindFirstValue(ClaimTypes.Email),
Id = user.FindFirstValue(ClaimTypes.NameIdentifier)
});
})
.RequireAuthorization();
app.MapGet("/public", () => Results.Ok("Anyone can see this!"));
await app.RunAsync();
Key points:
AddIdentityApiEndpoints<IdentityUser>()registers Identity services and configures Bearer token authentication in one call. No need to manually callAddAuthentication().AddBearerToken().AddEntityFrameworkStores<AppDbContext>()tells Identity to use your EF Core context for persistence.MapIdentityApi<IdentityUser>()maps all the/register,/login,/refresh, etc. routes.RequireAuthorization()protects an endpoint; returns401 Unauthorizedif no valid token is provided.
🗄️ Database Migration
1. Add the Initial Migration
dotnet ef migrations add InitialIdentitySchema
This generates a migration that creates all the Identity tables:
AspNetUsers- your user accountsAspNetRoles- role definitionsAspNetUserRoles- user-role assignmentsAspNetUserClaims- user claimsAspNetUserTokens- tokens (refresh tokens live here)AspNetUserLogins- external login providers
2. Apply the Migration
dotnet ef database update
Or apply it programmatically on startup:
// In Program.cs, before app.Run()
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync();
}
Important: The automatic migration approach is convenient in development but should be handled carefully in production. Consider using a deployment pipeline to run migrations separately.
📨 Using the Identity Endpoints
Register a New User
POST /register
Content-Type: application/json
{
"email": "john@example.com",
"password": "SecurePass123!"
}
Response: 200 OK (empty body on success)
Login and Get a Bearer Token
POST /login
Content-Type: application/json
{
"email": "john@example.com",
"password": "SecurePass123!"
}
Response:
{
"tokenType": "Bearer",
"accessToken": "eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600,
"refreshToken": "CfDJ8NzFy..."
}
The accessToken is a standard Bearer token. Use it in subsequent requests.
Call a Protected Endpoint
GET /me
Authorization: Bearer eyJhbGciOiJodHRwOi8vd3d3...
Response:
{
"email": "john@example.com",
"id": "a3f4b2c1-..."
}
Without the token (or with an expired one):
401 Unauthorized
Refresh an Expired Token
POST /refresh
Content-Type: application/json
{
"refreshToken": "CfDJ8NzFy..."
}
Response:
{
"tokenType": "Bearer",
"accessToken": "eyJhbGciOi...",
"expiresIn": 3600,
"refreshToken": "CfDJ8Abc..."
}
The old refresh token is invalidated and a new one is issued.
🔒 Protecting Endpoints with RequireAuthorization()
Basic Protection
// Must be authenticated (any valid token)
app.MapGet("/dashboard", () => "Your dashboard")
.RequireAuthorization();
Group-Level Authorization
Apply authorization to a whole group of routes at once instead of repeating .RequireAuthorization() on every endpoint:
var api = app.MapGroup("/api")
.RequireAuthorization();
api.MapGet("/orders", () => "All orders");
api.MapGet("/products", () => "All products");
api.MapPost("/orders", () => "Create order");
// All three require authentication
Allow Anonymous on Specific Endpoints
If you protect a group but need to open up certain routes:
api.MapGet("/health", () => "OK")
.AllowAnonymous();
Best Practices
1. Always Use HTTPS in Production
Bearer tokens are credentials - never send them over plain HTTP:
app.UseHttpsRedirection();
2. Don't Expose Sensitive Claims
// ✅ Only return what the client needs
app.MapGet("/me", (ClaimsPrincipal user) =>
{
return Results.Ok(new
{
Email = user.FindFirstValue(ClaimTypes.Email)
});
})
.RequireAuthorization();
🚀 Full Flow Recap
1. POST /register → Create account
2. POST /login → Get accessToken + refreshToken
3. GET /me → Pass Bearer token in Authorization header
4. POST /refresh → Get a new accessToken when expired
Key Takeaways
AddIdentityApiEndpoints<IdentityUser>()- wires up Identity + Bearer auth in one callMapIdentityApi<IdentityUser>()- maps/register,/login,/refresh, and more automaticallyIdentityDbContext- inherit from it to get all Identity tables for free- Migrations - run
dotnet ef migrations add+dotnet ef database update RequireAuthorization()- protects individual endpoints or entire route groups- Bearer tokens - pass as
Authorization: Bearer <token>header; refresh with/refresh
You went from zero to authenticated API in minutes.