Quick start: add cookie sign-in to an API
In this tutorial you build a minimal API that uses AuthEndpoints for cookie sign-in. By the end, a user can register, confirm their email, sign in, call a protected endpoint, and sign out. You use curl as the client, so you need no front end.
You need the .NET 10 SDK.
Create the project
Create an empty web project and add the packages:
dotnet new web -n AuthDemo
cd AuthDemo
dotnet add package AuthEndpoints
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
The tutorial uses SQLite so you can run it without a database server. Any EF Core provider works.
Wire the host
Replace Program.cs with this code:
using AuthEndpoints;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlite("Data Source=app.db"));
builder.Services.AddAuthEndpoints<AppUser, AppDbContext>(o =>
{
o.Passkeys.ServerDomain = "localhost";
});
builder.Services.AddTransient<IEmailSender<AppUser>, ConsoleEmailSender>();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
scope.ServiceProvider.GetRequiredService<AppDbContext>().Database.EnsureCreated();
}
app.UseAuthEndpoints();
app.MapAuthEndpoints<AppUser>();
app.MapGet("/me", (System.Security.Claims.ClaimsPrincipal user) => new { email = user.Identity!.Name })
.RequireAuthorization();
app.Run();
public class AppUser : IdentityUser { }
public class AppDbContext(DbContextOptions<AppDbContext> options) : IdentityDbContext<AppUser>(options) { }
public class ConsoleEmailSender(ILogger<ConsoleEmailSender> logger) : IEmailSender<AppUser>
{
public Task SendConfirmationLinkAsync(AppUser user, string email, string confirmationLink)
{
logger.LogInformation("Confirm {Email}: {Link}", email, confirmationLink);
return Task.CompletedTask;
}
public Task SendPasswordResetLinkAsync(AppUser user, string email, string resetLink)
{
logger.LogInformation("Reset link for {Email}: {Link}", email, resetLink);
return Task.CompletedTask;
}
public Task SendPasswordResetCodeAsync(AppUser user, string email, string resetCode)
{
logger.LogInformation("Reset code for {Email}: {Code}", email, resetCode);
return Task.CompletedTask;
}
}
Here is what the AuthEndpoints calls do:
AddAuthEndpoints<AppUser, AppDbContext>registers Identity with EF Core stores, the application cookie, passkeys, step-up, antiforgery, and the rate-limit policies.UseAuthEndpointsadds authentication, authorization, rate limiting, and antiforgery middleware. In a real app, call it after your exception-handling middleware.MapAuthEndpoints<AppUser>maps the routes under/identityand/account.
ConsoleEmailSender writes links to the log so you can follow them by hand. EnsureCreated builds the schema without migrations. Both are for local use only. See Production before you deploy.
Run the app
dotnet run
Note the URL in the Now listening on log line. The commands below use http://localhost:5265. Replace it with your URL. Open a second terminal for the curl commands.
Register a user
curl -i -H "Content-Type: application/json" \
-d '{"email":"ada@example.com","password":"Passw0rd!"}' \
http://localhost:5265/identity/register
The response is 200 OK. AuthEndpoints requires a confirmed account by default, so the user can't sign in yet.
Confirm the email
Find the Confirm ada@example.com line in the app log. Copy the link. The log HTML-encodes & as &, so change it back to &, then open the link:
curl -i "http://localhost:5265/identity/confirmEmail?userId=...&code=..."
The response is 200 OK.
Sign in
curl -i -c cookies.txt -b cookies.txt -H "Content-Type: application/json" \
-d '{"email":"ada@example.com","password":"Passw0rd!"}' \
http://localhost:5265/identity/login
The response is 200 OK with a Set-Cookie: .AspNetCore.Identity.Application=... header. curl stores the cookie in cookies.txt.
Call the protected endpoint with the cookie:
curl -b cookies.txt http://localhost:5265/me
The response is {"email":"ada@example.com"}.
Sign out
Sign-out changes state on a cookie session, so it needs a CSRF token. Try it without one first:
curl -i -c cookies.txt -b cookies.txt -X POST http://localhost:5265/identity/logout
The response is 400 Bad Request. Get a token, then send it in the RequestVerificationToken header:
curl -c cookies.txt -b cookies.txt http://localhost:5265/identity/csrfToken
curl -i -c cookies.txt -b cookies.txt -X POST \
-H "RequestVerificationToken: <csrfToken from the previous response>" \
http://localhost:5265/identity/logout
The response is 200 OK. Call /me again and you get 401 Unauthorized.
What you built
You have an API with password registration, email confirmation, cookie sign-in, and CSRF-protected sign-out. The same host also maps account management, two-factor, password reset, passkeys, and step-up routes. See Endpoints for the full list.
Native and mobile
To issue tokens instead of a cookie, pass AuthEndpointsSignIn.IdentityBearer as the first argument:
builder.Services.AddAuthEndpoints<AppUser, AppDbContext>(AuthEndpointsSignIn.IdentityBearer, o =>
{
o.Passkeys.ServerDomain = "example.com";
});
POST /identity/login then returns accessToken and refreshToken. See Bearer auth.
The bearer facade maps /identity/login, /identity/refresh, and /identity/logout. It does not map GET /identity/csrfToken. Passkeys stay on, and every passkey ceremony requires a CSRF token. A bearer token does not remove that requirement on anonymous requests such as passkey registration and passkey sign-in. To use passkeys with the bearer facade, map your own token endpoint:
using Microsoft.AspNetCore.Antiforgery;
app.MapGet("/identity/csrfToken", (IAntiforgery antiforgery, HttpContext context) =>
Results.Ok(new { csrfToken = antiforgery.GetAndStoreTokens(context).RequestToken }));
The client must keep the antiforgery cookie from that response and send the token in the RequestVerificationToken header. If you do not use passkeys, set o.Passkeys.Enabled = false instead.
Roles
To use roles, call the three-type overload so AddRoles runs before AddEntityFrameworkStores:
builder.Services.AddAuthEndpoints<AppUser, AppRole, AppDbContext>(o =>
{
o.Passkeys.ServerDomain = "example.com";
});
Your DbContext must be IdentityDbContext<AppUser, AppRole, TKey> or equivalent. Do not chain .AddRoles<TRole>() after the two-type overload.
Next steps
- Choose a sign-in stack if you need tokens, JWT, or OAuth
- Register users and Sign users in for each sign-in method
- Configuration for paths, passkeys, and JWT opt-in
- Production for HTTPS, email, and the passkey domain