aspnetcore icon indicating copy to clipboard operation
aspnetcore copied to clipboard

Minimal APIs : Endpoint route returning 404 not found.

Open izzyjere opened this issue 1 year ago • 8 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Describe the bug

I have an ASP.NET Core 7 WebAPI that uses minimal APIs however some endpoint routes are returning 404 Not Found even when the routes have been correctly configured.

             var customers = app.MapGroup("/api/customers");
            //These two endpoints are returning 404 Not Found.
            customers.MapGet("/", [Authorize] async (ICustomerService service) =>
            {
                return await service.GetAllAsync();
            });
            customers.MapGet("/{id}",
                [Authorize] async (ICustomerService service, [FromRoute] string id) =>
            {
                var result = await service.GetByUserGuid(id);
                if (result.Succeeded)
                {
                    return new Result<Customer>() { Data = result.Data, Succeeded = true };
                }
                else
                {
                    return new Result<Customer>() { Succeeded = result.Succeeded };
                }
            });
           //These two are working just fine
           app.MapPost("/api/login", [AllowAnonymous] async (ITokenService tokenService, [FromBody] TokenRequest request) =>
            {
                var res = await tokenService.LoginAsync(request);
                if (res.Succeeded)
                {
                    return new Result<TokenResponse>() { Succeeded = true, Message = res.Message, Data = res.Data };
                }
                else
                {
                    return new Result<TokenResponse>() { Succeeded = false, Message = "Incorect credentials", Data = new 
                         TokenResponse() };
                }
            });
            app.MapPost("/api/register", [AllowAnonymous] async (IUserService service, [FromBody] RegisterRequest request) =>
            {
               return await service.RegisterAsync(request, request?.Origin);
            });

Expected Behavior

No response

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

7.0.100

Anything else?

Screenshot 2022-12-28 105110

izzyjere avatar Dec 28 '22 08:12 izzyjere

I discovered that removing [Authorize] attribute works. Must be an issue with authorization so below is my JWT configuration.

        internal static IServiceCollection AddJwtAuthentication(this IServiceCollection services, AppConfiguration appConfig)
        {
            var key = Encoding.ASCII.GetBytes(appConfig.Secret);
            services
                .AddAuthentication(authentication =>
                {
                    authentication.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    authentication.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(bearer =>
                {
                    bearer.RequireHttpsMetadata = false;
                    bearer.SaveToken = true;
                    bearer.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(key),
                        ValidateIssuer = false,
                        ValidateAudience = false,
                        RoleClaimType = ClaimTypes.Role,
                        ClockSkew = TimeSpan.Zero
                    };


                    bearer.Events = new JwtBearerEvents
                    {
                        OnAuthenticationFailed = c =>
                        {
                            if (c.Exception is SecurityTokenExpiredException)
                            {
                                c.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                                c.Response.ContentType = "application/json";
                                var result = JsonConvert.SerializeObject(Result.Fail("Your Token Has Expired"));
                                return c.Response.WriteAsync(result);
                            }
                            else
                            {
                                c.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                                c.Response.ContentType = "application/json";
                                var result = JsonConvert.SerializeObject(Result.Fail("An unhandled error has occurred."));
                                return c.Response.WriteAsync(result);
                            }
                        },
                        OnChallenge = context =>
                        {
                            context.HandleResponse();
                            if (!context.Response.HasStarted)
                            {
                                context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                                context.Response.ContentType = "application/json";
                                var result = JsonConvert.SerializeObject(Result.Fail("You are not Authorized."));
                                return context.Response.WriteAsync(result);
                            }

                            return Task.CompletedTask;
                        },
                        OnForbidden = context =>
                        {
                            context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                            context.Response.ContentType = "application/json";
                            var result = JsonConvert.SerializeObject(Result.Fail("You are not authorized to access this resource."));
                            return context.Response.WriteAsync(result);
                        },
                    };
                });
            services.AddAuthorization();
            return services;
        }

izzyjere avatar Dec 28 '22 09:12 izzyjere

Can you show the entire pipeline wire up?

davidfowl avatar Dec 29 '22 03:12 davidfowl

Below is my Program.cs file. The same wire up works in .NET 6

var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

builder.Services.AddDatabase(connectionString);
builder.Services.AddJwtAuthentication(builder.Services.GetApplicationSettings(builder.Configuration));
builder.Services.AddInfrastructureServices();

builder.Services.RegisterSwagger();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<SignInMiddleware<User>>();
/* API ENDPOINTS */
app.MapEndpointRoutes();

app.SeedDatabase();
app.Run();

izzyjere avatar Dec 29 '22 04:12 izzyjere

It's hard to tell where the 404 might be coming from. Maybe set breakpoints in the various JWT events?

davidfowl avatar Dec 29 '22 05:12 davidfowl

I have tried setting breakpoints on each event. None of the breakpoints are reachable.

izzyjere avatar Dec 29 '22 06:12 izzyjere

I will try to set up a minimalistic project to reproduce the bug.

izzyjere avatar Dec 29 '22 06:12 izzyjere

@izzyjere one of the tricks I use to debug is adding a middleware to the pipeline at various places to see how for it got:

var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

builder.Services.AddDatabase(connectionString);
builder.Services.AddJwtAuthentication(builder.Services.GetApplicationSettings(builder.Configuration));
builder.Services.AddInfrastructureServices();

builder.Services.RegisterSwagger();

var app = builder.Build();

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

app.Use((context, next) =>
{
    // Put a breakpoint here
    return next(context);
});

app.UseAuthorization();
app.UseMiddleware<SignInMiddleware<User>>();
/* API ENDPOINTS */
app.MapEndpointRoutes();

app.SeedDatabase();
app.Run();

davidfowl avatar Dec 29 '22 08:12 davidfowl

Thank you that worked I was able to detect the cause. Calling app.UseHttpsRedirection() after app.UseAuthorization() fixed the problem.

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

izzyjere avatar Dec 29 '22 11:12 izzyjere

Why did you reopen this @davidfowl?

halter73 avatar Jan 05 '23 19:01 halter73

to let the bot close it

davidfowl avatar Jan 05 '23 19:01 davidfowl

Is it because of the autolock on closed issues? How long does that take?

halter73 avatar Jan 05 '23 19:01 halter73

No idea

davidfowl avatar Jan 05 '23 19:01 davidfowl

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

ghost avatar Jan 06 '23 22:01 ghost