santosh kumar patro
santosh kumar patro

Reputation: 8231

UnauthorizedException Overriding HTTP Status Code from 401 to 500 in .NET 8 API Endpoint

I am currently working with the AddJwtBearer method in a .NET 8 Web API application and encountering an issue with HTTP status codes during exception handling. When an unauthorized user attempts to access a particular API endpoint, an UnauthorizedException is thrown as expected at code level. However, instead of preserving the appropriate HTTP 401 Unauthorized status code, it is getting overridden and returning an HTTP 500 Internal Server Error status.

Here is a simplified overview of the current code flow:

An unauthorized user attempts to access a specific API endpoint. The system correctly identifies this as an unauthorized access attempt and throws an UnauthorizedException. Upon catching this exception, the system should be returning an HTTP 401 Unauthorized status code. Instead, the system is overriding the HTTP 401 status with an HTTP 500 status. I have tried troubleshooting this issue but have been unable to identify the root cause. I believe the AddJwtBearer method might be affecting this behavior, but I'm unsure about how to confirm this and, if confirmed, how to prevent it from happening.

If anyone has encountered this issue or has any insights on how to retain the correct HTTP 401 status code when throwing the UnauthorizedException, I would greatly appreciate your assistance.

Code :

private static IServiceCollection AddAuth(IServiceCollection services, IConfiguration config, bool isProduction)
{
    var authOptions = services.BindValidateReturn<OpenIdOptions>(config);

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(options =>
    {
        options.Authority = $"{authOptions.AADInstance}  {authOptions.AADTenantID}";
        options.Audience = authOptions.Audience;

        if (!isProduction)
        {
            options.RequireHttpsMetadata = false;
        }

        options.SaveToken = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            RequireAudience = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
        };
        options.Events = new JwtBearerEvents
        {
            OnChallenge = context =>
            {
                context.HandleResponse();
                if (!context.Response.HasStarted)
                {
                    throw new UnauthorizedException(context.Error!, context.ErrorDescription!);
                }

                return Task.CompletedTask;
            },
            OnForbidden = _ => throw new ForbiddenException()
        };
    });

    return services;
}

public class UnauthorizedException : CustomException
{
    public string Error { get; set; }
    public string Description { get; set; }
    public UnauthorizedException(string error = default!, string description = default!) : base(error, HttpStatusCode.Unauthorized)
    {
        Error = error;
        Description = description;
    }
}

public class CustomException : Exception
{
    public List<string>? ErrorMessages { get; }
    public HttpStatusCode StatusCode { get; }

    public CustomException(string message, HttpStatusCode statusCode = HttpStatusCode.InternalServerError)
        : base(message)
    {
        StatusCode = statusCode;
    }

    public CustomException(string message, List<string>? errors = default, HttpStatusCode statusCode = HttpStatusCode.InternalServerError)
        : base(message)
    {
        ErrorMessages = errors;
        StatusCode = statusCode;
    }
}

Validation was done using both Postman and swagger and result is having Status Code :500 instead of 401

Can anyone please help me here by providing their guidance? Any help would be greatly appreciated.

Upvotes: 0

Views: 189

Answers (1)

santosh kumar patro
santosh kumar patro

Reputation: 8231

I fixed the code by constructing proper response in this case :

private static IServiceCollection AddAuth(IServiceCollection services, IConfiguration config, bool isProduction, CancellationToken cancellation = default)
{
    var authOptions = services.BindValidateReturn<OpenIdOptions>(config);

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(options =>
    {
        options.Authority = $"{authOptions.AADInstance}  {authOptions.AADTenantID}";
        options.Audience = authOptions.Audience;

        if (!isProduction)
        {
            options.RequireHttpsMetadata = false;
        }

        options.SaveToken = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            RequireAudience = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ClockSkew = new TimeSpan(0, 0, 30)
        };
        options.Events = new JwtBearerEvents
        {
            OnChallenge = context =>
            {
                context.HandleResponse();
                if (!context.Response.HasStarted)
                {
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;

                    if (string.IsNullOrWhiteSpace(context.Error))
                        context.Error = "invalid_token";

                    if (string.IsNullOrWhiteSpace(context.ErrorDescription))
                        context.ErrorDescription = "This request requires a valid JWT access token to be provided";


                    if (context.AuthenticateFailure != null && context.AuthenticateFailure.GetType() == typeof(SecurityTokenExpiredException))
                    {
                        var authenticationException = context.AuthenticateFailure as SecurityTokenExpiredException;
                        context.Response.Headers.Append("x-token-expired", authenticationException?.Expires.ToString("o"));
                        context.ErrorDescription = $"The token expired on {authenticationException?.Expires:o}";
                    }

                    var errorResult = new ExceptionDetails()
                    {
                        Title = context.Error,
                        Detail = context.ErrorDescription!,
                        Status = (int)HttpStatusCode.Unauthorized,
                        Instance = context.Request.Path.Value
                    };

                    return context.Response.WriteAsJsonAsync(errorResult, default(JsonSerializerOptions), contentType: MediaTypeNames.Application.ProblemJson, cancellation);
                }

                return Task.CompletedTask;
            },
            OnForbidden = _ => throw new ForbiddenException()
        };
    });

    return services;
}

This fixed the issue now

Upvotes: 0

Related Questions