Authentication
Authentication answers “who are you?”. Before reaching for the full Identity system, it is worth building a login once with plain cookie authentication — you will understand exactly what Identity does for you later.
On this page · 8 min · Advanced
On this page
- In simple words
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 8 min
- Difficulty
- Advanced
- Updated
- Oct 29, 2026
Related tutorials
In simple words#
Authentication is the security guard checking your ID card at the gate: “Who are you?” Once checked, you get a visitor pass (cookie).
- Authentication
- Proving who you are.
- Cookie
- Pass sent with each request after login.
- UseAuthentication
- Middleware that reads the pass.
Learning objectives#
- Configure cookie authentication in Program.cs
- Sign users in with claims and sign them out
- Protect actions with [Authorize] and handle ReturnUrl
- Set safe cookie and expiry options
Explanation#
AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(…) registers the scheme. UseAuthentication() then reads the auth cookie on every request and fills HttpContext.User.
A signed-in user is a ClaimsPrincipal: a set of claims such as Name, Email and Role. HttpContext.SignInAsync(principal) encrypts them into the cookie; SignOutAsync deletes it.
[Authorize] on a controller or action requires a signed-in user. Anonymous users are redirected to LoginPath with ?ReturnUrl=… so they come back after logging in — always redirect with LocalRedirect or Url.IsLocalUrl to avoid open redirects.
Never store plain passwords. Verify against a hash — PasswordHasher<T> (PBKDF2) is built into ASP.NET Core. Give the same error for unknown users and wrong passwords.
Cookie options: HttpOnly (JavaScript cannot read it), SecurePolicy Always (HTTPS only), SameSite Lax, ExpireTimeSpan with SlidingExpiration, and AccessDeniedPath for signed-in users without permission.
Syntax#
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o => { o.LoginPath = "/Account/Login"; o.ExpireTimeSpan = TimeSpan.FromHours(8); });
…
app.UseAuthentication();
app.UseAuthorization();
await HttpContext.SignInAsync(new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme)));
Code example: Login, a protected page and logout#
// Program.cs
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Authentication.Cookies;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders(); // keep the demo output clean
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<UserStore>();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
options.ExpireTimeSpan = TimeSpan.FromHours(8);
options.SlidingExpiration = true;
options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // demo runs on http; use Always in production
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.Urls.Add("http://127.0.0.1:5117");
await app.StartAsync();
using var http = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false }) { BaseAddress = new Uri("http://127.0.0.1:5117") };
async Task<HttpResponseMessage> Login(string email, string password)
{
var form = await http.GetStringAsync("/Account/Login");
var token = Regex.Match(form, "__RequestVerificationToken\" type=\"hidden\" value=\"([^\"]+)").Groups[1].Value;
return await http.PostAsync("/Account/Login?ReturnUrl=%2FOrders", new FormUrlEncodedContent(
[new("Email", email), new("Password", password), new("__RequestVerificationToken", token)]));
}
void Show(string step, HttpResponseMessage r, string body = "") =>
Console.WriteLine(string.Join(' ', new[] { step.PadRight(24), quot;{(int)r.StatusCode}", r.Headers.Location?.ToString(), body }
.Where(part => !string.IsNullOrEmpty(part))));
var anonymous = await http.GetAsync("/Orders");
Show("GET /Orders (anonymous)", anonymous);
var wrong = await Login("asha@example.com", "guess");
Show("POST login (wrong)", wrong, Regex.Match(await wrong.Content.ReadAsStringAsync(), "<p class=\"error\">([^<]+)").Groups[1].Value);
var ok = await Login("asha@example.com", "Pa55word!");
var cookie = ok.Headers.GetValues("Set-Cookie").First();
Show("POST login (correct)", ok, cookie.Contains("httponly") ? "(cookie: httponly)" : "");
var orders = await http.GetAsync("/Orders");
Show("GET /Orders (signed in)", orders, await orders.Content.ReadAsStringAsync());
Show("POST logout", await http.PostAsync("/Account/Logout", null));
Show("GET /Orders (after)", await http.GetAsync("/Orders"));
await app.StopAsync();
// Services/UserStore.cs
using Microsoft.AspNetCore.Identity;
public record AppUser(string Email, string Name, string Role)
{
public string PasswordHash { get; set; } = "";
}
public class UserStore
{
private readonly PasswordHasher<AppUser> _hasher = new();
private readonly Dictionary<string, AppUser> _users = new(StringComparer.OrdinalIgnoreCase);
public UserStore()
{
var asha = new AppUser("asha@example.com", "Asha", "Customer");
asha.PasswordHash = _hasher.HashPassword(asha, "Pa55word!"); // store hashes, never passwords
_users[asha.Email] = asha;
}
public AppUser? Validate(string email, string password) =>
_users.TryGetValue(email, out var user) &&
_hasher.VerifyHashedPassword(user, user.PasswordHash, password) != PasswordVerificationResult.Failed
? user : null;
}
// Controllers/AccountController.cs
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
public class AccountController(UserStore users) : Controller
{
[HttpGet]
public IActionResult Login(string? returnUrl = null) => View();
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Login(string email, string password, string? returnUrl = null)
{
var user = users.Validate(email, password);
if (user is null)
{
ViewData["Error"] = "Invalid email or password."; // same message for both cases
return View();
}
Claim[] claims = [new(ClaimTypes.Name, user.Name), new(ClaimTypes.Email, user.Email), new(ClaimTypes.Role, user.Role)];
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
return LocalRedirect(Url.IsLocalUrl(returnUrl) ? returnUrl! : "/");
}
[HttpPost]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Index", "Home");
}
}
// Controllers/OrdersController.cs
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[Authorize] // signed-in users only
public class OrdersController : Controller
{
public IActionResult Index() =>
Content(quot;Orders for {User.Identity!.Name} ({User.FindFirstValue(ClaimTypes.Role)})");
}
@* Views/Account/Login.cshtml *@
<form method="post">
@if (ViewData["Error"] is string error) { <p class="error">@error</p> }
<input name="Email" /> <input name="Password" type="password" />
<button>Sign in</button>
</form>
GET /Orders (anonymous) 302 http://127.0.0.1:5117/Account/Login?ReturnUrl=%2FOrders POST login (wrong) 200 Invalid email or password. POST login (correct) 302 /Orders (cookie: httponly) GET /Orders (signed in) 200 Orders for Asha (Customer) POST logout 302 / GET /Orders (after) 302 http://127.0.0.1:5117/Account/Login?ReturnUrl=%2FOrders
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- An anonymous request to /Orders is redirected (302) to the login page with ReturnUrl.
- A wrong password re-renders the form with a generic message — no hint whether the email exists.
- The correct password builds a ClaimsPrincipal and signs in; the response sets an HttpOnly auth cookie and redirects to the ReturnUrl.
- With the cookie, [Authorize] lets the request through and User.Identity.Name and the Role claim are available.
- Logout deletes the cookie, so /Orders redirects to login again.
Real-world use case#
ShopSmart’s first version used exactly this for a single admin account. When customer accounts arrived (registration, password reset, lockout), the team moved to ASP.NET Core Identity — next lesson.
Common mistakes#
Hash with PasswordHasher (or Identity). Never write passwords to the database or logs.
An attacker can send /Account/Login?ReturnUrl=https://evil.example. Use LocalRedirect or Url.IsLocalUrl.
HttpContext.User stays anonymous and every [Authorize] request is redirected to login.
Best practices#
- HttpOnly, Secure (Always), SameSite cookies
- Generic login error messages
- Hash passwords; consider Identity for real user accounts
- Logout via POST with antiforgery
Remember this#
AddAuthentication().AddCookie() + UseAuthentication() + UseAuthorization().
SignInAsync(principal with claims) / SignOutAsync().
[Authorize] → anonymous users go to LoginPath?ReturnUrl=…
Practice exercise#
Your task: Add an AccessDeniedPath "/Account/Denied" and an action that shows “You do not have access to this page.”, then protect an AdminController with [Authorize(Roles = "Admin")].
Need a hint?
Show solution
// Program.cs
using Microsoft.AspNetCore.Authentication.Cookies;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.LoginPath = "/Account/Login";
o.AccessDeniedPath = "/Account/Denied";
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.Run();
// Controllers/AccountController.cs
using Microsoft.AspNetCore.Mvc;
public class AccountController : Controller
{
public IActionResult Denied() => Content("You do not have access to this page.");
}
// Controllers/AdminController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[Authorize(Roles = "Admin")]
public class AdminController : Controller
{
public IActionResult Index() => Content("Admin dashboard");
}
Mini quiz#
What does HttpContext.SignInAsync(principal) do with cookie authentication?
Want more? Practise all ASP.NET Core MVC MCQs · ASP.NET Core MVC interview questions
My notes#
Saved in this browser.