1

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
  • 1
    HTTP 405 method not allowed means your request is using an HTTP verb that isn't supported by the endpoint. You've mapped GET but not POST AFAICT. It is both expected and desirable behavior that the request is rejected before auth is attempted. Commented Feb 3, 2024 at 6:11

1 Answer 1

-2

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");
}); 
Sign up to request clarification or add additional context in comments.

1 Comment

i make this change but i still returning 405 status code not 401

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.