This episode secures ASP.NET Core applications: ASP.NET Core Identity for user management, the authentication pipeline, JWT authentication and authorization policies, OAuth2 and OpenID Connect integration, and secure headers, CORS, and token management.

An open API without security is an invitation to be attacked. Every real application must know who its users are (authentication), what those users are allowed to do (authorization), and how to keep their identities safe.
ASP.NET Core provides a complete security foundation: Identity for user management, a pluggable authentication middleware, and support for JWT and OAuth2/OpenID Connect.
Episode 12 builds the security layer step by step: Identity, JWT, authorization policies, OAuth2 integration, and secure headers and CORS.
ASP.NET Core Identity handles users, password hashing, roles, and claims, with storage in EF Core. Add the package and register its services:
dotnet add src/Toko.Api package Microsoft.AspNetCore.Identity.EntityFrameworkCorevar builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TokoContext>(o =>
o.UseSqlite("Data Source=toko.db"));
builder.Services.AddIdentityApiEndpoints<IdentityUser>()
.AddEntityFrameworkStores<TokoContext>();
var app = builder.Build();
app.MapIdentityApi<IdentityUser>();
app.Run();AddIdentityApiEndpoints provides standard endpoints such as register and login. The authentication pipeline works as middleware: each request gets an identity from a token or cookie, then the authorization policy decides whether the request is forwarded.
For APIs consumed by mobile applications or SPAs, JWT is the primary choice. A JWT is a signed string containing the user's claims:
dotnet add src/Toko.Api package Microsoft.AspNetCore.Authentication.JwtBearerbuilder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://id.contoh.id";
options.Audience = "toko-api";
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));
});AddJwtBearer validates the token signature and claims on every request. The AdminOnly policy ensures only users with the Admin role can access certain endpoints.
Apply policies with an attribute on the controller or action:
[ApiController]
[Route("api/admin")]
public class AdminController : ControllerBase
{
[HttpGet]
[Authorize(Policy = "AdminOnly")]
public IActionResult Get() => Ok("Hanya admin yang bisa melihat ini.");
[HttpDelete]
[Authorize]
public IActionResult Hapus() => Ok("Semua pengguna yang login boleh menghapus.");
}The [Authorize] attribute without arguments just requires a logged-in user, while [Authorize(Policy = "AdminOnly")] requires a specific role.
For single sign-on with an external provider, register an OpenID Connect or OAuth handler:
builder.Services.AddAuthentication()
.AddOpenIdConnect("google", options =>
{
options.ClientId = "client-id-anda";
options.ClientSecret = "client-secret-anda";
options.Authority = "https://accounts.google.com";
options.ResponseType = "code";
options.Scope.Add("email");
});With the configuration above, the login flow follows the OAuth2/OpenID Connect protocol: the application redirects the user to the provider, receives a code, then exchanges it for tokens. This pattern avoids managing passwords yourself — for both security and user experience.
Browsers enforce security policies through HTTP headers. ASP.NET Core can add security headers automatically:
app.UseHsts();
app.UseHttpsRedirection();
app.UseCors(policy => policy
.WithOrigins("https://app.contoh.id")
.AllowAnyHeader()
.AllowAnyMethod());UseHsts tells the browser to always use HTTPS. The CORS configuration above only allows the origin https://app.contoh.id — never use AllowAnyOrigin without careful thought, because it opens your API to any website.
JWTs should be short-lived and paired with refresh tokens. Limit the validity period with TokenValidationParameters and always send tokens in the Authorization header, not in the URL:
curl -H "Authorization: Bearer <token>" https://api.contoh.id/api/adminThe curl -H "Authorization: Bearer <token>" command above sends the token through a safe header. Never put tokens in a query string — they'll be recorded in server logs and browser history.
Key takeaways:
RequireRole separate access rights per endpoint.In the next episode 13 we deepen the security side of code: secure coding and best practices — input validation and sanitization, the OWASP Top 10 for .NET, the Data Protection API, and preventing injection, XSS, CSRF, and sensitive data leakage.