Securing APIs is one of the most important aspects of modern application development. Whether you're building public APIs, mobile backends, or microservices, authentication and authorization ensure that only trusted clients and users can access protected resources.
OAuth has become the industry standard for delegated authorization, allowing applications to access resources on behalf of users without exposing their credentials. OAuth 2.1 builds upon OAuth 2.0 by simplifying the specification, removing insecure flows, and promoting modern security practices such as Proof Key for Code Exchange (PKCE).
In this article, you'll learn the core concepts of OAuth 2.1, how it integrates with ASP.NET Core APIs, and the best practices for building secure authentication systems.
What Is OAuth 2.1?
OAuth 2.1 is an evolution of OAuth 2.0 that consolidates security best practices and removes outdated authorization flows.
Unlike traditional username/password authentication, OAuth separates authentication from resource access.
A simplified request flow looks like this:
Client Application
│
Authorization Server
│
Access Token
│
ASP.NET Core API
The client obtains an access token from the authorization server and includes it in API requests.
The API validates the token before granting access to protected resources.
Why OAuth 2.1?
OAuth 2.1 improves security by removing older, less secure authorization flows and encouraging safer defaults.
Key improvements include:
Mandatory use of PKCE for authorization code flows
Removal of the Implicit Grant flow
Removal of the Resource Owner Password Credentials (ROPC) flow
Stronger guidance for refresh token handling
Emphasis on secure redirect URIs
These changes reduce common attack vectors while simplifying implementation.
OAuth Roles
OAuth defines several participants.
Resource Owner – The user who owns the protected data.
Client – The application requesting access.
Authorization Server – Authenticates users and issues tokens.
Resource Server – The API that validates tokens and serves protected resources.
Understanding these roles makes it easier to design secure authentication flows.
Authorization Code Flow with PKCE
For modern web, mobile, and desktop applications, the Authorization Code flow with PKCE is the recommended approach.
The process is:
User
│
Client Application
│
Authorization Server
│
Authorization Code
│
Access Token
│
ASP.NET Core API
PKCE protects authorization codes from interception by requiring the client to prove possession of a cryptographically generated secret during token exchange.
Today, this is considered the standard flow for interactive applications.
Configuring JWT Bearer Authentication
Most OAuth-protected APIs validate JWT access tokens.
Register JWT bearer authentication:
using Microsoft.AspNetCore.Authentication.JwtBearer;
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://your-auth-server";
options.Audience = "products-api";
});
Enable authentication and authorization:
app.UseAuthentication();
app.UseAuthorization();
The API now validates incoming bearer tokens automatically.
Protecting Endpoints
Use the Authorize attribute to secure API endpoints.
using Microsoft.AspNetCore.Authorization;
[Authorize]
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
return Ok();
}
}
Unauthenticated requests receive an HTTP 401 Unauthorized response.
Authorization policies can further restrict access based on claims or roles.
Understanding Access Tokens
Access tokens represent the permissions granted to a client.
Typical JWT claims include:
Subject (sub)
Audience (aud)
Issuer (iss)
Expiration (exp)
Scopes
Roles
APIs should validate these claims before processing requests.
Avoid relying solely on token presence—always verify that the token is intended for your API and hasn't expired.
Refresh Tokens
Access tokens are intentionally short-lived.
When they expire, clients use refresh tokens to request new access tokens without requiring users to authenticate again.
A typical lifecycle looks like this:
User Login
│
Access Token
│
Expires
│
Refresh Token
│
New Access Token
Refresh tokens should be:
Never expose refresh tokens to browser-based JavaScript applications unless the authentication platform explicitly supports secure handling.
Scopes and Permissions
Scopes define what an access token is allowed to do.
Examples:
products.read
products.write
orders.read
orders.manage
Rather than granting broad access, assign only the permissions required for each client.
Following the principle of least privilege reduces security risks if a token is compromised.
OAuth 2.1 Best Practices
| Practice | Why It Matters |
|---|
| Use Authorization Code with PKCE | Protects against authorization code interception |
| Validate JWT claims | Prevents unauthorized access |
| Keep access tokens short-lived | Limits exposure if compromised |
| Store refresh tokens securely | Reduces token theft risk |
| Apply least-privilege scopes | Minimizes unnecessary permissions |
| Always use HTTPS | Protects tokens during transmission |
Best Practices
Use a trusted identity provider instead of implementing OAuth manually.
Protect every production API with HTTPS.
Validate issuer, audience, and token expiration.
Keep access tokens short-lived.
Store refresh tokens securely and rotate them when appropriate.
Apply authorization policies in addition to authentication.
Log authentication failures for auditing and troubleshooting.
Regularly review scopes and client permissions.
Common Mistakes
Using Deprecated OAuth Flows
OAuth 2.1 removes the Implicit Grant and Resource Owner Password Credentials flows because they no longer meet modern security requirements. Use the Authorization Code flow with PKCE instead.
Trusting Tokens Without Validation
A JWT should never be accepted without validating its signature, issuer, audience, and expiration. Improper validation can allow unauthorized access.
Granting Excessive Permissions
Avoid issuing tokens with broad scopes by default. Restrict permissions to only what each client requires.
Writing a Custom Authentication Server
Implementing OAuth correctly is complex. Unless you have specialized requirements, use a well-established identity provider that supports OAuth 2.1 and OpenID Connect.
Conclusion
OAuth 2.1 provides a modern, secure foundation for protecting ASP.NET Core APIs. By removing outdated authorization flows and promoting best practices such as PKCE, secure token handling, and least-privilege access, it simplifies the process of building robust authentication systems.
ASP.NET Core's built-in authentication middleware makes it straightforward to validate bearer tokens and secure API endpoints, while external identity providers handle the complexity of user authentication and token issuance.
When combined with HTTPS, proper token validation, secure refresh token management, and carefully designed authorization policies, OAuth 2.1 enables .NET developers to build APIs that are both secure and scalable. Adopting these practices early helps protect applications as they grow and evolve in increasingly distributed environments.