Authentication and authorization are related, but they solve different problems.
Authentication determines who is making a request. Authorization determines whether that identity is allowed to perform the requested operation.
Treating these concerns as interchangeable can leave APIs vulnerable even when they require users to sign in.
Configure Authentication Deliberately
ASP.NET Core supports different authentication schemes, including cookies and bearer tokens. The appropriate choice depends on the application and its clients.
For an API that accepts bearer tokens, configure a suitable token-validation handler and validate the issuer, audience, signature, and lifetime according to the trusted identity provider’s configuration.
Do not merely decode a JWT and assume its claims are trustworthy. Token validation is what establishes that the token meets the application’s security requirements.
Require Authorization by Default
Register authorization services and middleware:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication()
.AddJwtBearer();
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/api/profile", () =>
{
return Results.Ok(new { Message = "Authorized" });
})
.RequireAuthorization();
app.Run();
This is a structural example, not a complete production authentication configuration. A real application must configure the bearer handler with the correct authority or token-validation parameters and ensure that middleware and endpoint routing are configured appropriately.
The RequireAuthorization() call makes the endpoint require an authenticated, authorized request.
Use Policies for Specific Requirements
Some endpoints require more than a valid identity. For example, an administrative operation may require a particular role or permission.
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ManageUsers", policy =>
policy.RequireAuthenticatedUser()
.RequireRole("Administrator"));
});
Apply the policy to the relevant endpoint:
app.MapDelete("/api/users/{id}", DeleteUser)
.RequireAuthorization("ManageUsers");
Roles are useful for broad responsibilities. More complex systems may need claims-based or resource-based authorization, especially when access depends on ownership, tenant boundaries, or the particular resource being modified.
Check Resource Ownership
A user being authenticated does not mean they can access every resource.
For example, an endpoint that loads a document by ID should verify that the current user has permission to access that document. Do not trust a user ID supplied by the client as proof of ownership.
Depending on the data model, ownership can be enforced through a database query constrained by the authenticated identity or through a dedicated authorization handler.
Avoid Inconsistent Security Rules
If each endpoint implements authorization differently, small mistakes can create security gaps.
Use shared policies and reusable authorization handlers where they accurately represent the application’s rules. Keep public endpoints explicitly identified, and review newly added routes to ensure they have the intended protection.
Also consider background jobs and internal service calls. They may not use HTTP authentication, but they still require clear authorization and trust boundaries.
Test Both Allowed and Denied Requests
Security tests should cover more than a successful administrator request.
Test unauthenticated access, authenticated users without the required role, resource ownership violations, cross-tenant access, expired tokens, and malformed credentials.
A secure system must reject unauthorized requests consistently, without exposing sensitive resource information in error responses.
Conclusion
ASP.NET Core APIs need explicit authentication and authorization rules. Validate tokens correctly, apply policies consistently, and enforce resource-level access rather than assuming that a signed-in user can access every record.
Security should be part of endpoint design and testing from the beginning, not an additional layer applied after the API is complete.