Why does it return status code 405 instead of 401 unauthorized?

102 Views Asked by At

Here in my ASP.NET Core 6 Web API project, when I try to make a request to an authorized endpoint and I'm not authenticated, the API returns a status code of 405, but isn't it supposed to return 401 ?

enter image description here

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
using Microsoft.OpenApi.Models;
using MyBGList.Constants;
using MyBGList.Models;
using MyBGList.Swagger;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.MSSqlServer;

var builder = WebApplication.CreateBuilder(args);
// built-in logging provider configuration
builder.Logging
    .ClearProviders()
    .AddConsole()
    .AddDebug();

//Serilog configuration
builder.Host.UseSerilog((hostBuilderContext, loggerConfg) =>
{
    loggerConfg.ReadFrom.Configuration(hostBuilderContext.Configuration);
    loggerConfg.WriteTo.File("Logs/EventLogs.txt", rollingInterval: RollingInterval.Minute);
    loggerConfg.WriteTo.Console();
    loggerConfg.WriteTo.File("Logs/Errors.txt"
        , outputTemplate:
            "{Timestamp:HH:mm:ss} [{Level:u3}] " +
            "[{MachineName} #{ThreadId}] " +
            "{Message:lj}{NewLine}{Exception}"
        , restrictedToMinimumLevel: LogEventLevel.Error
        , rollingInterval: RollingInterval.Day);

    loggerConfg.Enrich.WithMachineName();
    loggerConfg.Enrich.WithThreadId();
    loggerConfg.Enrich.WithThreadName();
    loggerConfg.WriteTo.MSSqlServer(connectionString: hostBuilderContext.Configuration.GetConnectionString("DefaultConnection"),
        sinkOptions: new MSSqlServerSinkOptions()
        {
            AutoCreateSqlTable = true,
            TableName = "LogEvents"
        },
        columnOptions: new ColumnOptions()
        {
            AdditionalColumns = new SqlColumn[]
            {
                new SqlColumn()
                {
                    ColumnName = "SourceContext",
                    PropertyName="SourceContext",
                    DataType= System.Data.SqlDbType.NVarChar
                }
            }
        });
}, writeToProviders: true);

// Add services to the container.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
builder.Services.AddResponseCaching(options =>
{
    options.SizeLimit = 50 * 1024 * 1024;       //50MB
    options.MaximumBodySize = 32 * 1024 * 1024; //32MB
});

builder.Services.AddMemoryCache();

builder.Services.AddControllers(options =>
{
    options.CacheProfiles.Add("NoCache", new CacheProfile() { NoStore = true });
    options.CacheProfiles.Add("Any-60", new CacheProfile() { Location = ResponseCacheLocation.Any, Duration = 60 });
    options.ModelBindingMessageProvider.SetValueIsInvalidAccessor(x => $"The value {x} is invalid");
    options.ModelBindingMessageProvider.SetValueMustBeANumberAccessor(x => $"The value {x} must be a number");
    options.ModelBindingMessageProvider.SetAttemptedValueIsInvalidAccessor((x, y) => $"The value {x} is not valid for {y}");
    options.ModelBindingMessageProvider.SetMissingKeyOrValueAccessor(() => "The value is required");
}
);
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
    options.SuppressModelStateInvalidFilter = true;
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.ParameterFilter<SortOrderFilter>();
    options.ParameterFilter<SortColumnFilter>();

    options.ParameterFilter<SortColumnFilter>();
    options.ParameterFilter<SortOrderFilter>();

    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Description = "Please enter token",
        Name = "Authorization",
        Type = SecuritySchemeType.Http,
        BearerFormat = "JWT",
        Scheme = "bearer"
    });
    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type=ReferenceType.SecurityScheme,
                    Id="Bearer"
                }
            },
            Array.Empty<string>()
        }
    });

});

builder.Services.AddCors(config =>
{
    config.AddDefaultPolicy(options =>
    {
        options.WithOrigins(builder.Configuration["AllowedOrigins"]);
        options.AllowAnyHeader();
        options.AllowAnyMethod();
    });
    config.AddPolicy(name: "AnyOrigin", options =>
    {
        options.AllowAnyOrigin();
        options.AllowAnyHeader();
        options.AllowAnyMethod();
    });
});

builder.Services.AddIdentity<ApiUser, IdentityRole>(options =>
{
    options.Password.RequiredLength = 8;
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireUppercase = true;


}).AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme =
    options.DefaultForbidScheme =
    options.DefaultAuthenticateScheme =
    options.DefaultSignOutScheme =
    options.DefaultSignInScheme =
    JwtBearerDefaults.AuthenticationScheme;
})
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateIssuer = true,
            ValidIssuer = builder.Configuration["JWT:Issuer"],
            ValidateAudience = true,
            ValidAudience = builder.Configuration["JWT:Audience"],
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(builder.Configuration["JWT:SigningKey"]))

        };
    });

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

if (app.Configuration.GetValue<bool>("UseDeveloperExceptionPage"))
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler(action =>
    {
        action.Run(async context =>
        {
            var exceptionHandler =
            context.Features.Get<IExceptionHandlerPathFeature>();
            var details = new ProblemDetails();
            details.Detail = exceptionHandler?.Error.Message;
            details.Extensions["traceId"] =
            System.Diagnostics.Activity.Current?.Id
            ?? context.TraceIdentifier;
            details.Type =
            "https://tools.ietf.org/html/rfc7231#section-6.6.1";
            details.Status = StatusCodes.Status500InternalServerError;
            await context.Response.WriteAsync(
            System.Text.Json.JsonSerializer.Serialize(details));
            app.Logger.LogError(CustomLogEvents.Error_Get, exceptionHandler?.Error, "An unhandeled exception ocurres");
        });
    }); ;
}

app.UseHttpsRedirection();
app.UseCors();
app.UseResponseCaching();

app.UseAuthentication();
app.UseAuthorization();

app.Use((context, next) =>
{
    context.Response.Headers["cache-control"] = "no-cahce, no-store";
    return next.Invoke();
});

app.MapGet("cache/test/1", (HttpContext httpContext) =>
{
    httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
    {
        NoCache = true,
        NoStore = true,
    };
    return Results.Ok();
});
app.MapGet("/auth/test/1", [Authorize][EnableCors("AnyOrigin")] () =>
{
    return Results.Ok("You are authorized");
});
app.MapGet("/error/test", () => { throw new InvalidDataException(); });

app.MapControllers();
app.MapDefaultControllerRoute();

app.Run();

As shown in the screenshot, the API returns status code 405 and I think the "not allowed method" status code has no relationship with authorization but this actually happens when I add the [Authorize] attribute to the endpoint at /auth/test/1.

Please if anyone can help me understand why this behavior is happening, explain it to me in detail and thank you

1

There are 1 best solutions below

1
Sandeep On

Just a small adjustment in code: the [Authorize] and [EnableCors] attributes are usually applied at the controller or action method level, not directly within the route definition in the app.MapGet method. Consider updating your code as follows:

 app.MapGet("/auth/test/1", [Authorize, EnableCors("AllowAll")] () =>
{
    return Results.Ok("You are authorized");
});