Example frontmatter
Last updated: Mon Apr 21 2025
First we will see how authentication works in a web application and what are the key terminalogies used in the asp .net core.
1. Authentication Flow — Knowing “Who You Are”
-
User Action:
The user enters credentials (username & password) on the login page.
-
HTTP Request Sent:
The browser sends these credentials in an HTTP request to the web server.
-
Server Verification:
- The server validates credentials against the user data store (database).
- If correct, it creates an Identity object (user’s identity and claims).
- This Identity forms the Security Context — the core representation of the logged-in user.
-
Cookie Creation:
- The Security Context is serialized and encrypted into a cookie.
- The cookie is sent back to the browser in the HTTP response header.
- Cookies are domain-bound; they cannot be reused across different domains for security reasons.
-
Subsequent Requests:
- Each new request from the browser automatically includes this cookie.
- The server decrypts and deserializes the cookie to rebuild the Security Context.
- This re-verification ensures the user’s identity is still valid for every request.
👉 Key idea: Authentication = establishing and recognizing who the user is.
Even after login, each request re-authenticates by re-reading the encrypted cookie.

Once authenticated 3 and 4 will repeats.
2. Authorization Flow — Knowing “What You Can Do”
- After authentication, the web server checks whether the Security Context grants permission to access the requested resource (page, API, or data).
- If authorized, the server responds with the requested data or view.
- If not, it returns an access-denied or redirection response.
👉 Key idea: Authorization = deciding what actions an authenticated user is allowed to perform.
4. Conceptual Hierarchy
| Concept | Meaning | Example |
|---|---|---|
| Identity | Represents the user (username, roles, claims). | User.Identity.Name |
| Security Context | Stores the current user’s identity for the request. | HTTP Context’s User object |
| Authentication | Verifies who the user is. | Login + cookie verification |
| Authorization | Determines what the user can access. | Role/Policy checks |
5. Big Picture
- Authentication: Verifies credentials → creates Security Context → serializes into cookie → reused in future requests.
- Authorization: Uses the Security Context to enforce permissions.
- Both processes work seamlessly together in ASP.NET Core using ASP.NET Core Identity or custom authentication handlers.
💡 Takeaway
Authentication establishes identity, Authorization enforces access.
ASP .Net Core Project
The cookie acts as the secure bridge between browser and server — carrying encrypted user identity across HTTP requests.
In essence, the lecture builds intuition about how ASP.NET Core identifies, tracks, and validates users before granting access.
Login.cshtml.cs
@page
@model WebApplicationAuth.Pages.Account.Login
@{
}
<div class="container border" style="padding:20px">
<form method="post">
<div class="row mb-3">
<div class="col-2">
<label asp-for="Credential.Username"></label>
</div>
<div class="col-5">
<input type="text" asp-for="Credential.Username">
</div>
</div>
<div class="row mb-3">
<div class="col-2">
<label asp-for="Credential.Password"></label>
</div>
<div class="col-5">
<input type="password" asp-for="Credential.Password">
</div>
</div>
</form>
</div>
This is how we have to bind the data between the frontend and the .cs file

Terms you’ll see everywhere
- Claim: a fact about the user (
"name" = "Sarathi","role" = "Admin"). - Identity: a set of claims issued by one auth mechanism (e.g., cookie, JWT).
- Principal: the user in code → a container of one or more identities.
- Authentication Scheme: a named mechanism + handler (e.g.,
"Cookies","Bearer"). - Ticket: the serialized auth state stored by a scheme (e.g., in a cookie).
- Security Context: the
HttpContext.User(aClaimsPrincipal) attached to the request.
Cookie based authentication
Next we are goinf to add Authentication services in the Program.cs. Before that we will see the flow of the control here. Lets say, our ASP .Net Core server receives a request,
GET /UserDashboard
Cookie: .User.Cookie=ABC123; .Admin.Cookie=XYZ789
Here in the cookies header we have two values. The server side middleware must decide which authentication handler should process with the cookie. It will look for the handlers which is registered in the options. In Program.cs assume that we added the following code,
builder.Services.AddAuthentication()
.AddCookie("UserCookieAuth", options => options.Cookie.Name = ".User.Cookie")
.AddCookie("AdminCookieAuth", options => options.Cookie.Name = ".Admin.Cookie");
UseAuthentication()
├──→ Calls AuthenticateAsync() on each registered scheme
│ ↓
│ For "UserCookieAuth" → looks for ".User.Cookie"
│ For "AdminCookieAuth" → looks for ".Admin.Cookie"
│
└──→ First one that succeeds sets HttpContext.User
Inside each cookie handler
Each cookie handler does this:
- Reads its configured cookie name (
options.Cookie.Name) - Looks for that key in the incoming
Cookie:header - If found:
- Extracts its value (
ABC123) - Decrypts and validates it via Data Protection
- Rebuilds the
ClaimsPrincipal - Marks authentication as successful for that scheme
- Extracts its value (
- If not found → skip silently (no cookie for me)
Browser → sends Cookie: .User.Cookie=ABC123
↓
ASP.NET has scheme table:
[UserCookieAuth → CookieName:.User.Cookie]
[AdminCookieAuth → CookieName:.Admin.Cookie]
↓
"UserCookieAuth" handler finds a match (.User.Cookie)
↓
Decrypts, rebuilds ClaimsPrincipal
↓
HttpContext.User = that principal
↓
Request authenticated successfully under UserCookieAuth scheme
ASP.NET Core allows multiple auth handlers to exist, but only one will be the default authenticator — the one used to populate HttpContext.User. Here two authentication handlers are registers with a unique scheme name("UserCookieAuth" and "AdminCookieAuth") and in the each handler its own cookie name (.User.Cookie or .Admin.Cookie)
builder.Services.AddAuthentication("UserCookieAuth")
.AddCookie("UserCookieAuth", o => o.Cookie.Name = ".User.Cookie")
.AddCookie("AdminCookieAuth", o => o.Cookie.Name = ".Admin.Cookie");
If you have multiple cookies, you can still manually authenticate with another scheme.
var adminResult = await HttpContext.AuthenticateAsync("AdminCookieAuth");
if (adminResult.Succeeded)
{
var adminUser = adminResult.Principal;
}
or protect routes explicitly
[Authorize(AuthenticationSchemes = "AdminCookieAuth")]
TLDR
| Concept | Description |
|---|---|
| Cookie: | Raw key–value sent by browser (.User.Cookie=ABC123) |
| Cookie Handler: | The object that knows how to decrypt and validate that cookie |
| Scheme Name: | Identifier for that handler (UserCookieAuth) |
| Mapping: | SchemeName → CookieName is known from Program.cs setup |
| Decision logic: | The handler that matches the cookie name decrypts and authenticates it |
| Default Scheme: | The one used automatically to populate HttpContext.User |
🔐 ASP.NET Core Cookie Authentication — Under the Hood
1️⃣ Login & Cookie Creation
- After credentials are verified, ASP.NET Core creates a Security Context (ClaimsPrincipal).
- This context is serialized, encrypted, and stored in a cookie.
- Browser stores this cookie (e.g.,
MyCookieAuth) and automatically sends it in subsequent requests.
2️⃣ Authentication Middleware’s Role
- The Authentication Middleware runs on every request.
- It:
- Reads the incoming HTTP request headers (specifically
Cookie:). - Uses the configured authentication scheme (e.g.,
"MyCookieAuth"). - Finds the matching cookie.
- Decrypts & deserializes it into a
ClaimsPrincipal. - Populates
HttpContext.User(the Security Context).
- Reads the incoming HTTP request headers (specifically
3️⃣ Configuring Authentication Properly
-
Must call:
app.UseAuthentication(); app.UseAuthorization();— in that order.
-
Place
UseAuthentication()after routing and before authorization. -
This ensures cookies are interpreted before
[Authorize]checks.
4️⃣ Default Scheme Logic
- In older .NET versions (before .NET 9):
-
You had to explicitly specify the default authentication scheme:
builder.Services.AddAuthentication("MyCookieAuth")
-
- In .NET 9+:
-
If there’s only one scheme, it becomes the default automatically.
-
Still, best practice is to explicitly define both:
builder.Services.AddAuthentication("MyCookieAuth") .AddCookie("MyCookieAuth", options => { ... });
-
5️⃣ Authentication Handler
- Each scheme has a corresponding authentication handler (e.g., Cookie Handler).
- Handler knows:
- Which cookie name to read.
- How to decrypt it.
- How to reconstruct the identity (claims).
- The handler’s method
AuthenticateAsync()performs this translation.
6️⃣ Key Takeaways
| Concept | Description |
|---|---|
| Cookie | Encrypted container storing user’s claims. |
| Scheme name | Logical key grouping all auth-related config. |
| Middleware | Reads cookie, decrypts, rebuilds HttpContext.User. |
| Order in pipeline | UseAuthentication() → UseAuthorization(). |
| Default Scheme | Used automatically if only one is registered. |
| Purpose | To populate the Security Context for every request. |
🎯 Essence
Cookie = Encrypted snapshot of your Security ContextAuthentication Middleware = The mechanism that restores itScheme = The configuration name connecting both
Authentication and Authorization Architecture
// Default authentication mechanism is mentioned in the AddCookie method.
// Here it is MyCookieAuth
builder.Services.AddAuthentication("MyCookieAuth")
.AddCookie("MyCookieAuth", options =>
{
options.Cookie.Name = "MyCookieAuth";
options.LoginPath = "/Account/Login";
});
const int probationMonths = 3;
//This is for authorization policy
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireClaim("Admin"));
options.AddPolicy("MustBelongToHRDepartment", policy => policy.RequireClaim("Department", "HR"));
options.AddPolicy("HRManagerOnly", policy => policy
.RequireClaim("Department", "HR")
.RequireClaim("Manager")
//Below is a custom policy
.Requirements.Add(new HrManagerProbationRequirement(probationMonths)));
});
builder.Services.AddSingleton<IAuthorizationHandler, HrManagerProbationRequirementHandler>();
// To make the cookie persistent we can set the property in AuthenticationProperties
var authProperties = new AuthenticationProperties
{
IsPersistent = Credential.RememberMe
};
await HttpContext.SignInAsync("MyCookieAuth", principal, authProperties);
<partial> - this is to render condition based. We used it to display Login/Logout based on the user authentication.
To define custom requirement
using Microsoft.AspNetCore.Authorization;
namespace WebApp_UnderTheHood.Authorization
{
public class HRManagerProbationRequirement : IAuthorizationRequirement
{
public HRManagerProbationRequirement(int probationMonths)
{
ProbationMonths = probationMonths;
}
public int ProbationMonths { get; }
}
public class HRManagerProbationRequirementHandler : AuthorizationHandler<HRManagerProbationRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HRManagerProbationRequirement requirement)
{
if (!context.User.HasClaim(x => x.Type == "EmploymentDate"))
return Task.CompletedTask;
if (DateTime.TryParse(context.User.FindFirst(x => x.Type == "EmploymentDate")?.Value, out DateTime employmentDate))
{
var period = DateTime.Now - employmentDate;
if (period.Days > 30 * requirement.ProbationMonths)
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}
Created a new project WebAPIController and we checked a GET request from HRManager page to Web API to fetch the weather forecast items.
For that, we have to inject the client’s (Backend) address in Program.cs and name it.
Then, create a HttpclientFactory in the respective page with the given name.
// "OurWebAPI" -> name of the client and its base address is registered
// Used in HRManager Page to GET Weather Forecasts list
builder.Services.AddHttpClient("OurWebAPI", client =>
{
client.BaseAddress = new Uri("https://localhost:7279");
});
public class HrManager : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public HrManager(IHttpClientFactory httpClientFactory)
{
this._httpClientFactory = httpClientFactory;
}
// To display the weather details in the table we are binding
// It may not return a value, so it is a nullable
[BindProperty]
public List<WeatherForecastDTO>? WeatherForecastItems { get; set; }
public async Task OnGetAsync()
{
var client = _httpClientFactory.CreateClient("OurWebAPI");
WeatherForecastItems = await client.GetFromJsonAsync<List<WeatherForecastDTO>>("WeatherForecast");
// WeatherForeCast is the controller name. Routing is based on the controller name.
}
}
This is how we call APIs of other projects. We used DTOs to map the objects.
Token Based Authentication

Authenticate method in a controller
Here the username and password are hardcoded for simplicity. If valid credentials are received, we are returing token if not we are returning 401 Unauthorized. We can see that the steps are almost similar to the cookies based authentication.
//WebAPI/Controllers/AuthController.cs
private readonly IConfiguration _configuration;
//Constructor
public AuthController(IConfiguration configuration)
{
this._configuration = configuration; // To read Secrete key
}
[HttpPost]
public IActionResult Authenticate([FromBody]Credential credential)
{
if (credential.Username == "admin" && credential.Password == "password")
{
// Creating the security context
var claims = new List<Claim> {
new Claim(ClaimTypes.Name, "admin"),
new Claim(ClaimTypes.Email, "admin@mywebsite.com"),
new Claim("Department", "HR"),
new Claim("Admin", "true"),
new Claim("Manager", "true"),
new Claim("EmploymentDate", "2025-01-01")
};
var expiresAt = DateTime.UtcNow.AddMinutes(10);
return Ok(new
{
access_token = CreateToken(claims, expiresAt),
expires_at = expiresAt,
});
}
ModelState.AddModelError("Unauthorized", "You are not authorized to access the endpoint.");
var problemDetails = new ProblemDetails
{
Title = "Unauthorized",
Status = StatusCodes.Status401Unauthorized,
};
return Unauthorized(problemDetails);
}
How to create a token
Required configurations are
- Claims
- Hashing algorithm
- Expires at
Token handlers create JWT with the help of the above mentioned configs. In in the below code, claims are added in dictionary and then passed to the token handler.
//WebAPI/Controllers/AuthController.cs
private string CreateToken(List<Claim> claims, DateTime expiresAt)
{
var claimsDic = new Dictionary<string, object>();
if (claims is not null && claims.Count > 0)
{
foreach (var claim in claims)
{
claimsDic.Add(claim.Type, claim.Value);
}
}
var tokenDescriptor = new SecurityTokenDescriptor
{
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(
System.Text.Encoding.UTF8.GetBytes(configuration["SecretKey"] ?? string.Empty)),
SecurityAlgorithms.HmacSha256Signature),
Claims = claimsDic,
Expires = expiresAt
NotBefore = DateTime.UtcNow,
};
var tokenHandler = new JsonWebTokenHandler();
return tokenHandler.CreateToken(tokenDescriptor);
}
Reading Tokens
We have already seen how to create and send a token from the server to the client.
The following example shows how to register or inject it in
For authorization, we added a policy named AdminOnly, which requires the presence of the Adminclaim.
var secretKey = builder.Configuration["SecretKey"];
// Install Authentication.JwtBearer Nuget Package
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey??string.Empty)),
ClockSkew = TimeSpan.Zero // Disable the default clock skew of 5 minutes
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireClaim("Admin"));
});
Admin claim was already included in the token response. Therefore, in subsequent requests, we need to check for this claim.
//WebApplicationAuth/Pages/HRManager.cshtml.cs
//......
[Authorize(policy:"AdminOnly")]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
//....
}
Once we have implemented JWT generation on the server side, the next step is to consume it from the client and use it to access protected API endpoints.
Here’s how we can do that in the OnGetAsync() method:
//WebApplicationAuth/Pages/HRManager.cshtml.cs
public async Task OnGetAsync()
{
// Create an HTTP client instance using the registered factory
var httpClient = _httpClientFactory.CreateClient("OurWebAPI");
// Step 1: Authenticate the user and get a JWT
// TODO: Fetch Username and Password from forms
var response = await httpClient.PostAsJsonAsync(
"Auth", new Credentials {Username = "admin", Password = "password"});
// Ensure the response was successful
response.EnsureSuccessStatusCode();
// Step 2: Read and deserialize the JWT token
var strJwt = await response.Content.ReadAsStringAsync();
var token = JsonConvert.DeserializeObject<JwtToken>(strJwt);
// Step 3: Attach the token to the Authorization header
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", token?.access_token?? string.Empty);
// Step 4: Use the token to call a protected API endpoint
WeatherForecastItems = await httpClient.
GetFromJsonAsync<List<WeatherForecastDTO>>("WeatherForecast");
}
Store and Reuse Token in Session
When working with APIs secured by JWT tokens, it’s inefficient to request a new token every time you make an API call. Instead, you can store the token in session and reuse it until it expires. Here’s how to do that.
Step 1: Add sessions support in Program.cs
builder.Services.AddSession(options =>
{
options.Cookie.HttpOnly = true;
options.IdleTimeout = TimeSpan.FromMinutes(20);
options.Cookie.IsEssential = true;
});
Then add session middleware
app.UseSession();
Step 2: Retrieve and Store Token in the Page Model
In your Razor Page or Controller, implement logic to check if the token exists in session.
If not, authenticate to get a new token and store it for reuse.
public async Task OnGetAsync()
{
JwtToken token = new JwtToken();
// Try to retrieve token from session
var strTokenObj = HttpContext.Session.GetString("access_token");
if (string.IsNullOrEmpty(strTokenObj))
{
token = await Authenticate();
}
else
{
token = JsonConvert.DeserializeObject<JwtToken>(strTokenObj) ?? new JwtToken();
}
// If token is invalid or expired, re-authenticate
if (token == null ||
string.IsNullOrWhiteSpace(token.AccessToken) ||
token.ExpiresAt <= DateTime.UtcNow)
{
token = await Authenticate();
}
// Use token for authorized API call
var httpClient = httpClientFactory.CreateClient("OurWebAPI");
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token?.AccessToken ?? string.Empty);
weatherForecastItems =
await httpClient.GetFromJsonAsync<List<WeatherForecastDTO>>("WeatherForecast")
?? new List<WeatherForecastDTO>();
}
Step 3: Authenticate and Save Token in Session
The Authenticate() method requests a token from your API and saves it in the session for future use.
private async Task<JwtToken> Authenticate()
{
var httpClient = httpClientFactory.CreateClient("OurWebAPI");
// Send credentials to authenticate
var res = await httpClient.PostAsJsonAsync("auth", new Credential
{
UserName = "admin",
Password = "password"
});
res.EnsureSuccessStatusCode();
// Deserialize token and store in session
string strJwt = await res.Content.ReadAsStringAsync();
var token = JsonConvert.DeserializeObject<JwtToken>(strJwt);
HttpContext.Session.SetString("access_token", strJwt);
// Example API call using the new token
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token?.AccessToken ?? string.Empty);
weatherForecastItems =
await httpClient.GetFromJsonAsync<List<WeatherForecastDTO>>("WeatherForecast")
?? new List<WeatherForecastDTO>();
return token ?? new JwtToken();
}
ASP .Net Core Identity
ASP.NET Core Identity:
- Is an API that supports user interface (UI) login functionality.
- Manages users, passwords, profile data, roles, claims, tokens, email confirmation, and more.
Three essential parts of Identity
- UI
- Functionalities
- Data Store
Required Packages
Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.SqlServer
Migrations
dotnet ef migrations add InitialCreate dotnet ef database update
// Import required namespaces
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
namespace WebApp.Pages.Account
{
// Razor PageModel for handling User Registration
public class RegisterModel : PageModel
{
// The UserManager class is a part of ASP.NET Core Identity
// It provides APIs to manage users: create, update, delete, validate passwords, etc.
private readonly UserManager<IdentityUser> _userManager;
// Constructor injection for UserManager via Dependency Injection
public RegisterModel(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
// BindProperty allows automatic two-way data binding between
// Razor Page input fields and this property during form submission.
[BindProperty]
public RegisterViewModel registerViewModel { get; set; } = new();
// Called when the Register page is requested via HTTP GET
// Currently empty since no preloading or initialization is required
public void OnGet() { }
// Handles the POST request when the user submits the registration form
// The 'async' keyword is used because UserManager methods are asynchronous
public async Task<IActionResult> OnPostAsync()
{
// Step 1: Validate the model — ensure all required fields are valid
if (!ModelState.IsValid)
{
// Return the same page with validation error messages
return Page();
}
// Step 2: Create a new IdentityUser instance
// IdentityUser is the default user entity provided by ASP.NET Core Identity
// It includes properties like Email, UserName, Id, etc.
var identityUser = new IdentityUser
{
Email = registerViewModel.Email, // Assign email from form input
UserName = registerViewModel.Email // Using Email as the username
};
// Step 3: Attempt to create the user in the Identity system
// This also performs password policy validation (length, uppercase, etc.)
var result = await _userManager.CreateAsync(identityUser, registerViewModel.Password);
// Step 4: Handle result
if (result.Succeeded)
{
// If registration is successful, redirect the user to the Index page
// (You can change this to redirect to Login or Confirmation page)
return RedirectToPage("/Index");
}
else
{
// If creation failed, Identity returns one or more error messages
// Loop through all errors and add them to ModelState so Razor can display them
foreach (var error in result.Errors)
{
// Each error has a Code (e.g., DuplicateEmail) and Description (e.g., "Email already taken")
ModelState.AddModelError(error.Code, error.Description);
}
// Return the same page with validation errors displayed
return Page();
}
}
}
// ViewModel for capturing registration input fields from the user
public class RegisterViewModel
{
// Email field is required and must be in valid email format
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email address")]
public string Email { get; set; }
// Password field is required and masked in the UI due to DataType.Password
[Required(ErrorMessage = "Password is required")]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
| Concept | Purpose |
|---|---|
UserManager<IdentityUser> | Core Identity service that handles user creation, validation, password rules, and persistence. |
[BindProperty] | Enables two-way binding between Razor Page form fields and server-side properties. |
ModelState.IsValid | Ensures that all validation attributes (like [Required], [EmailAddress]) pass before proceeding. |
_userManager.CreateAsync() | Creates a new user and automatically hashes the password before saving to the database. |
ModelState.AddModelError() | Collects error messages from Identity (e.g., duplicate email, weak password) and displays them in Razor. |
RedirectToPage("/Index") | Redirects the user after successful registration. Usually changed later to /Account/Login or /Account/ConfirmEmail. |