aspnetcore/security/authentication/configure-jwt-bearer-authentication.md
JWT (JSON Web Token) Bearer Authentication is commonly utilized for APIs. While it operates similarly to cookie authentication, the identity provider issues a JWT or tokens upon a successful authentication. These tokens can then be sent to other servers to authenticate, unlike cookies which are only sent back to the issuing domain. A JWT is a self-contained token that encapsulates information for an API resource or a client. The client which requested the JWT can request data from an API resource using the Authorization header and a bearer token.
JWT Bearer Authentication provides:
JwtBearerHandler, bearer tokens are essential for authentication. The JwtBearerHandler validates the token and extracts the user's identity from its claims.For an introduction to JWT Bearer Authentication, see JSON Web Tokens. View or download sample code
This article covers the following areas:
There are numerous types of tokens and formats. Generating your own access tokens or ID tokens is discouraged, except for testing purposes. Self-created tokens that do not adhere to established standards:
We recommend using OpenID Connect 1.0 or an OAuth standard for creating access tokens intended for API access.
Access tokens:
See The OAuth 2.0 Authorization Framework
Access tokens can be either application access tokens or delegated access tokens. The tokens have different claims and are managed and stored differently. An application access token is typically stored once in the app until it expires, while a delegated access token is stored per user, either in a cookie or in a secure server cache.
We recommend using delegated user access tokens whenever a user is involved. Downstream APIs can request a delegated user access token on behalf of the authenticated user.
Access tokens can be used as bearer tokens or sender-constrained tokens to access resources. Sender-constrained tokens require the requesting client to prove possession of a private key to use the token. Proving possession of a private key guarantees the token can't be used independently. Sender-constrained tokens can be implemented in two ways:
ID tokens are security tokens that confirm a user’s successful authentication. The tokens allow the client to verify the user’s identity. The JWT token server issues ID tokens containing claims with user information. ID tokens are always in JWT format.
ID tokens should never be used to access APIs.
There are many types of tokens, including access and ID tokens, as specified by OpenID Connect and OAuth standards. Refresh tokens can be used to refresh a UI app without re-authenticating the user. OAuth JAR tokens can securely send authorization requests. Verifiable credentials flows utilize JWT types for issuing or verifying credentials. It is crucial to use tokens according to the specifications. See the standards links provided later in this article for more information.
When using JWT access tokens for API authorization, the API grants or denies access based on the provided token. If the request is not authorized, a 401 or 403 response is returned. The API shouldn't redirect the user to the identity provider to obtain a new token or request additional permissions. The app consuming the API is responsible for acquiring an appropriate token. This ensures a clear separation of concerns between the API (authorization) and the consuming client app (authentication).
[!NOTE] HTTP also allows returning 404 for not authorized, so as to not leak information about the existence of resources to unauthorized clients.
A 401 Unauthorized response indicates that the provided access token doesn't meet the required standards. This could be due to several reasons, including:
aud) or issuer (iss), are missing or invalid.[!NOTE] From the HTTP Semantics RFC 9110: The server generating a 401 response MUST send a WWW-Authenticate header field (Section 11.6.1) containing at least one challenge applicable to the target resource.
The OAuth specifications provide detailed guidelines on the required claims and their validation.
A 403 Forbidden response typically indicates that the authenticated user lacks the necessary permissions to access the requested resource. This is distinct from authentication issues, e.g. an invalid token, and is unrelated to the standard claims within the access token.
In ASP.NET Core, you can enforce authorization using:
Requirements and policies: Define custom requirements, e.g., "Must be an administrator" and associate them with policies. Role-based authorization: Assign users to roles e.g., "Admin," "Editor", and restrict access based on those roles.
When an API uses JWT access tokens for authorization, the API only validates the access token, not on how the token was obtained.
OpenID Connect (OIDC) and OAuth 2.0 provide standardized, secure frameworks for token acquisition. Token acquisition varies depending on the type of app. Due to the complexity of secure token acquisition, it's highly recommended to rely on these standards:
HttpContext.GetTokenAsync("access_token").The Microsoft.AspNetCore.Authentication.JwtBearer Nuget package can be used to validate the JWT bearer tokens.
JWT bearer tokens should be fully validated in an API. The following should be validated:
The following claims are required for OAuth 2.0 access tokens: iss, exp, aud, sub, client_id, iat, and jti.
If any of these claims or values are incorrect, the API should return a 401 response.
A basic implementation of the xref:Microsoft.Extensions.DependencyInjection.JwtBearerExtensions.AddJwtBearer%2A can validate just the audience and the issuer. The signature must be validated so that the token can be trusted and that it hasn't been tampered with.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(jwtOptions =>
{
jwtOptions.Authority = "https://{--your-authority--}";
jwtOptions.Audience = "https://{--your-audience--}";
});
The xref:Microsoft.Extensions.DependencyInjection.JwtBearerExtensions.AddJwtBearer%2A method provides multiple configurations. Some secure token providers use a non-standard metadata address and the parameter can be setup explicitly. The API can accept multiple issuers or audiences.
Explicitly defining the parameters is not required. The definitions depends on the access token claim values and the secure token server used to validate the access token. You should use the default values if possible.
See Mapping claims MapInboundClaims details.
builder.Services.AddAuthentication()
.AddJwtBearer("some-scheme", jwtOptions =>
{
jwtOptions.MetadataAddress = builder.Configuration["Api:MetadataAddress"];
// Optional if the MetadataAddress is specified
jwtOptions.Authority = builder.Configuration["Api:Authority"];
jwtOptions.Audience = builder.Configuration["Api:Audience"];
jwtOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidAudiences = builder.Configuration.GetSection("Api:ValidAudiences").Get<string[]>(),
ValidIssuers = builder.Configuration.GetSection("Api:ValidIssuers").Get<string[]>()
};
jwtOptions.MapInboundClaims = false;
});
APIs often need to accommodate access tokens from various issuers. Supporting multiple token issuers in an API can be accomplished by:
xref:Microsoft.AspNetCore.Authorization.AuthorizationBuilder.SetFallbackPolicy%2A can be used to require authentication for all requests even to endpoints without an [Authorize] attribute. xref:Microsoft.AspNetCore.Authorization.AuthorizationBuilder.SetDefaultPolicy%2A configures the policy used for endpoints with the [Authorize] attribute and already defaults to requiring authenticated users. See the require authenticated users documentation for more details.
var requireAuthPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
builder.Services.AddAuthorizationBuilder()
.SetFallbackPolicy(requireAuthPolicy);
The xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute attribute can also be used to force the authentication. If multiple schemes are used, the bearer scheme generally needs to be set as the default authentication scheme or specified via [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme]).
Authorization in controllers:
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
Authorization in Minimal APIs:
app.MapGet("/hello", [Authorize] () => "Hi");
Insecure handling of access tokens, such as weak authentication or storing tokens in vulnerable client-side storage, can lead to significant security vulnerabilities. For example, storing access tokens directly in the browser using local storage, session storage, or web workers. The following section contains best practices for apps using and creating access tokens.
Standards like OpenID Connect or OAuth should always be used when creating access tokens. Access tokens should not be created in production apps without adhering to the security precautions outlined in this article. Creating access tokens should be limited to test scenarios.
Asymmetric keys should always be used when creating access tokens. The public key is available in the well known endpoints and the API clients can validate the signature of the access token using the public key.
You should NOT create an access token from a username/password request. Username/password requests aren't authenticated and are vulnerable to impersonation and phishing attacks. Access tokens should only be created using an OpenID Connect flow or an OAuth standard flow. Deviating from these standards can result in an insecure app.
For secure web apps, a backend is required to store access tokens on a trusted server. Only a secure HTTP only cookie is shared on the client browser. See the OIDC authentication documentation for how to do this in an ASP.NET Core web app.
APIs occasionally need to access user data from downstream APIs on behalf of the authenticated user in the calling app. While implementing an OAuth client credentials flow is an option, it necessitates full trust between the two API apps. A more secure approach involves using a zero-trust strategy with a delegated user access token. This approach:
There are several ways to implement a zero-trust strategy with a delegated user access token:
This is a good way to implement this requirement but it's complicated if you must implement the OAuth flow.
Using the Microsoft Identity Web authentication library is the easiest and a secure approach. It only works with Microsoft Entra ID, Microsoft Entra External ID.
For more information, see Microsoft identity platform and OAuth 2.0 On-Behalf-Of flow.
This approach is not difficult to implement but the access token has access to all downstream APIs. Yarp reverse proxy can be used to implement this.
This is easy to implement but the client application has full application access and not a delegated access token. The token should be cached in the client API application.
[!NOTE] Any app-to-app security also works. Certificate authentication, or in Azure, a managed identity can be used.
When using access tokens in a client application, the access tokens must be rotated, persisted, and stored on the server. In a web app, cookies are used to secure the session and can be used to store tokens via the xref:Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions.SaveTokens%2A property.
xref:Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions.SaveTokens%2A doesn't refresh access tokens automatically, but this functionality is planned for a future release. In the meantime, you can manually refresh the access token as demonstrated in the Blazor Web App with OIDC documentation or use a third-party NuGet package, such as Duende.AccessTokenManagement.OpenIdConnect. For more information, see Duende token management.
[!NOTE] If deploying to production, the cache should work in a multi-instance deployment. A persistent cache is normally required.
Some secure token servers encrypt the access tokens. Access tokens do not require any format. When using OAuth introspection, a reference token is used instead of an access token. A client (UI) application should never open an access token as the access token is not intended for this. Only an API for which the access token was created for should open the access token.
YARP (Yet Another Reverse Proxy) is a useful technology for handling HTTP requests and forwarding the requests to other APIs. YARP can implement security logic for acquiring new access credentials. YARP is frequently used when adopting Backend for Frontend (BFF) security architecture.
:::moniker range=">= aspnetcore-9.0"
For Blazor examples that use YARP to implement the BFF pattern, see the following articles:
:::moniker-end
:::moniker range="< aspnetcore-9.0"
For a Blazor example that uses YARP to implement the BFF pattern, see xref:blazor/security/blazor-web-app-oidc?pivots=with-yarp-and-aspire.
:::moniker-end
For more information, see auth0: The Backend for Frontend Pattern.
Integration tests and containers with access tokens can be used to test secure APIs. Access tokens can be created using the dotnet user-jwts tool.
[!WARNING] Ensure that security problems are not introduced into the API for testing purposes. Testing becomes more challenging when delegated access tokens are used, as these tokens can only be created through a UI and an OpenID Connect flow. If a test tool is used to create delegated access tokens, security features must be disabled for testing. It's essential that these features are only disabled in the test environment.
Create dedicated and isolated test environments where security features can safely be disabled or modified. Ensure these changes are strictly limited to the test environment.
Swagger UI and Curl are great UI tools for testing APIs. For the tools to work, the API can produce an OpenAPI document and this can be loaded into the client testing tool. A security flow to acquire a new access token can be added to the API OpenAPI file.
[!WARNING] Do not deploy insecure security test flows to production.
When implementing a Swagger UI for an API, you should normally not deploy the UI to production as the security must be weakened to allow this to work.
Refer to the following document:
Mapping, customizing, and transforming claims in ASP.NET Core