diff --git a/docs/en/Community-Articles/2025-04-20-IDX10204/POST.md b/docs/en/Community-Articles/2025-04-20-IDX10204/POST.md new file mode 100644 index 0000000000..5de0650d11 --- /dev/null +++ b/docs/en/Community-Articles/2025-04-20-IDX10204/POST.md @@ -0,0 +1,170 @@ +# Common Errors in JWT Bearer Authentication + +When implementing JWT Bearer authentication in an ABP(tiered) application, you might occasionally encounter errors starting with `IDX`. These errors are related to JWT Bearer Token validation and this article will help you understand and resolve them. + +## Enable JWT Bearer authentication + +Your API project usually contains the following code, which enables JWT Bearer authentication and makes it as the default authentication scheme. + +We simply configure the JWT's `Authority` and `Audience` properties, and it will work fine. + +```csharp +context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = "https://localhost:44301/"; //configuration["AuthServer:Authority"]; + options.Audience = "MyProjectName"; + }); +``` + +> `AddJwtBearer` and `AddAbpJwtBearer` will do the same thing, but `AddAbpJwtBearer` is recommended. + +## JWT authentication process + +Let's take a look at how the above code works. + +A JWT Token usually consists of three parts: `Header`, `Payload`, and `Signature`. + +- `Header`: Contains the type and signing algorithm of the token +- `Payload`: Contains the claims of the token, including `sub`, `aud`, `exp`, `iat`, `iss`, `jti`, `preferred_username`, `given_name`, `role`, `email`, etc. +- `Signature`: The cryptographic signature of the token used to verify its authenticity + +Here is an example of a JWT Token issued by `AuthServer(OpenIddict)`: + +The `Header` part: + +![JWT Header](header.png) + +The `Payload` part: + +![JWT Payload](payload.png) + +### TokenValidationParameters + +In the `JwtBearerOptions`, there is a `TokenValidationParameters` property, which is used to validate the JWT Token. + +The default implementation for JWT Token validation is `JsonWebTokenHandler`, which comes from the [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/) package. + +We didn't set the `TokenValidationParameters` property in the code above, so the default values below will be used: + +```csharp +//... +TokenValidationParameters.ValidateAudience = true +TokenValidationParameters.ValidAudience = "MyProjectName" +TokenValidationParameters.ValidAudiences = null + +TokenValidationParameters.ValidateIssuer = true +TokenValidationParameters.ValidIssuer = null +TokenValidationParameters.ValidIssuers = null +//... +``` + +### JWT Bearer Token Validation Process + +During JWT Bearer authentication, API website will get the token from the HTTP request and validate it. + +The `JsonWebTokenHandler` will get the `OpenID Connect` metadata from the `AuthServer`, it will be used in the validation process, the current metadata request address is: https://localhost:44301/.well-known/openid-configuration , it is a fixed address calculated from the `Authority` property. + +First, the token's Signature is verified using the public key obtained from `OpenID Connect` metadata(https://localhost:44301/.well-known/jwks). + +Then, the payload is validated. The payload is a JSON object containing essential information such as the `token type`, `expiration time`, `issuer`, and `audience` etc. + +Most of the validation problems we may encounter are payload validation failures, for example: + +#### Lifetime + +If the token in your request has expired, the validation will fail. You will see the exception information like `IDX10230` in the log. + +#### Audience + +The `ValidAudience` of `TokenValidationParameters` is `MyProjectName`, the `aud` in the payload of the token is also `MyProjectName`, if the token does not contain `aud` or the `aud` does not match, the validation will fail. You may see the exception information like `IDX10206`, `IDX10277` or `IDX10208`. + +> If the `ValidateAudience` of `TokenValidationParameters` is `false`, then the `aud` will not be validated. + +#### Issuer + +The default value of `TokenValidationParameters.ValidateIssuer` is `true`, it requires the token's payload to contain the `issuer` field, and it must match one of `TokenValidationParameters.ValidIssuer` or `TokenValidationParameters.ValidIssuers`. + +> The default value of `ValidIssuer` or `ValidIssuers` is `null`, it will use the `issuer` from the `OpenID Connect` metadata as the default value. + +1. If the token's payload does not contain the `issuer` field, you may see the error `IDX10211`. +2. If the API website cannot get the `OpenID Connect` metadata from AuthServer website, the validation will fail. You may see the error `IDX10204`, the full exception message is: `IDX10204: Unable to validate issuer. validationParameters.ValidIssuer is null or whitespace AND validationParameters.ValidIssuers is null or empty.` +3. If the `issuer` does not match, the validation will fail. You may see the error `IDX10205` in the log. + +> If the `ValidateIssuer` of `TokenValidationParameters` is `false`, then the `issuer` will not be validated. + +> Please note that `OpenIddict` will use the current HTTP request information as the value of `issuer`. If the AuthServer website is deployed behind a reverse proxy or similar deployment configurations, the `issuer` in the token may not be the value you expect. In this case, please specify it manually. + +```csharp +PreConfigure(serverBuilder => +{ + serverBuilder.SetIssuer("https://localhost:44301/"); +}); +``` + +## Troubleshooting + +To troubleshoot any `IDX` errors during JWT authentication, you can enable detailed logging by configuring the `identitymodel` logs as follows: + +```csharp +using System.Diagnostics.Tracing; +using Microsoft.IdentityModel.Logging; + +public class Program +{ + public async static Task Main(string[] args) + { + IdentityModelEventSource.ShowPII = true; + IdentityModelEventSource.Logger.LogLevel = EventLevel.Verbose; + var wilsonTextLogger = newTextWriterEventListener("Logs/identitymodel.txt"); + wilsonTextLogger.EnableEvents(IdentityModelEventSource.Logger, EventLevel.Verbose); + + //... + } +} +``` + +Additionally, you can enable `OpenIddict`'s `Verbose` logs for more detailed debugging information: + +```csharp +var loggerConfiguration = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) + .MinimumLevel.Override("OpenIddict", LogEventLevel.Verbose) + .Enrich.FromLogContext() + .WriteTo.Async(c => c.File("Logs/logs.txt")) +``` + +## Summary + +For JWT authentication, you need to pay attention to the following key points: + +1. Ensure your API website can communicate with the AuthServer properly +2. Verify that the `aud` claim in your token matches the expected audience +3. Confirm that the `issuer` claim in your token is valid and matches the configuration + +You can customize the `JwtBearerOptions`'s `TokenValidationParameters` to modify the validation rules to meet your actual needs. + +For example, if your `issuer` needs to support multiple subdomains, you can use the [Owl.TokenWildcardIssuerValidator](https://github.com/maliming/Owl.TokenWildcardIssuerValidator) library to customize the validation. + +```csharp +services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = "https://abp.io"; + options.Audience = "abp_io"; + + options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator; + options.TokenValidationParameters.ValidIssuers = new[] + { + "https://{0}.abp.io" + }; + }); +``` + +## References + +- [Configure JWT bearer authentication in ASP.NET Core]([https://learn.microsoft.com/en-us/aspnet/core/security/authentication/jwt-auth?view=aspnetcore-8.0](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/configure-jwt-bearer-authentication)) +- [OpenIddict](https://github.com/openiddict/openiddict-core) +- [IdentityModel](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) +- [Owl.TokenWildcardIssuerValidator](https://github.com/maliming/Owl.TokenWildcardIssuerValidator) diff --git a/docs/en/Community-Articles/2025-04-20-IDX10204/header.png b/docs/en/Community-Articles/2025-04-20-IDX10204/header.png new file mode 100644 index 0000000000..a71c9a66ff Binary files /dev/null and b/docs/en/Community-Articles/2025-04-20-IDX10204/header.png differ diff --git a/docs/en/Community-Articles/2025-04-20-IDX10204/payload.png b/docs/en/Community-Articles/2025-04-20-IDX10204/payload.png new file mode 100644 index 0000000000..a83d6e1051 Binary files /dev/null and b/docs/en/Community-Articles/2025-04-20-IDX10204/payload.png differ