I have an ASP.NET web api which uses cookie authentication with ASP.NET Identity.
When an unauthorized/ unauthenticated user tries to hit secured endpoint it always shows Not found 404 instead of 401/ 403. I want the proper status codes to be shown. I do not even need the redirect part.
My code WORKS the same if I add AddAuthentication().AddCookie() with its configuration, as well as with ConfigureApplicationCookie(); Also I can configure the name of the cookie, which I can see in the browser. But when I configure the AccessDeniedPath = "/User/Login" and I have such endpoint nothing happens. Also I tried creating custom endpoint which was [HttpGet("Unauthorized")] and returned Unauthorized(); Then set the AccessDeniedPath to "/User/Unauthorized"; Nothing happened.
The security part stops working even for the proper logged users when I use AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(), even if I decored the endpoints itself with the attribute for the Auth scheme for cookies.
I am not sure for a scenario which I havent tried and simply it does not return the proper status code. I even tried with such code:
AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(config =>
{
config.Cookie.Name = "Identity.Cookie";
config.LoginPath = "/User/Login";
config.LogoutPath = "/User/Logout";
config.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = (int)401;
return Task.CompletedTask;
};
});
None of this seems to work in any freaking way.
I will share you the registration of the services, if I did something wrong with them.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddAntiforgery();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var connectionString = builder.Configuration.GetConnectionString("Default");
builder.Services.AddDbContext<DatabaseContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddAutoMapper(typeof(Program));
builder.Services.AddDefaultIdentity<User>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<DatabaseContext>();
builder.Services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "Identity.Cookie";
config.LoginPath = "/User/Login";
config.LogoutPath = "/User/Logout";
config.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = (int)401;
return Task.CompletedTask;
};
});
builder.Services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
options.User.RequireUniqueEmail = false;
});
//EF Identity
builder.Services.AddIdentityCore<User>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
})
//Injecting the services and DB in the DI containter
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<DatabaseContext>()
.AddDefaultTokenProviders();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Administrator", policy =>
policy.RequireRole("Administrator"));
options.AddPolicy("MasterAdmin", policy =>
policy.RequireRole("MasterAdmin"));
options.AddPolicy("RegularUser", policy =>
policy.RequireRole("RegularUser"));
options.AddPolicy("ProhibitAdminAction", policy =>
policy.Requirements.Add(new ProhibitAdminActionRequirement()));
});
builder.Services.AddHostedService<MessageBusSubscriber>(); //RabbitMQ Async Bus Factory
builder.Services.AddSingleton<ConnectionState>();
builder.Services.AddTransient<IPublishNewMessage, PublishNewMessage>();
builder.Services.AddTransient<IUpdateUserBalance, UpdateUserBalance>();
builder.Services.AddSingleton<IEventProcessor, EventProcessor>();
builder.Services.AddScoped<IIdentityRepository, IdentityRepository>();
builder.Services.AddScoped<ISignInRepository, SignInRepository>();
builder.Services.AddTransient<IUserValidations, UserValidations>();
builder.Services.AddTransient<IGetPrincipalUser, GetPrincipalUser>();
builder.Services.AddTransient<IGetAllUsers, GetAllUsers>();
builder.Services.AddTransient<IGetUser, GetUser>();
builder.Services.AddTransient<ICreateUser, CreateUser>();
builder.Services.AddTransient<IUpdateUserAdmin, UpdateUserAdmin>();
builder.Services.AddTransient<IDeleteUser, DeleteUser>();
builder.Services.AddTransient<ILoginUser, LoginUser>();
builder.Services.AddTransient<ILogoutUser, LogoutUser>();
builder.Services.AddTransient<IRegisterUser, RegisterUser>();
builder.Services.AddTransient<IAssignRoleUser, AssignRoleUser>();
builder.Services.AddTransient<IUnassignRoleUser, UnassignRoleUser>();
builder.Services.AddTransient<IUpdateLoggedUser, UpdateLoggedUser>();
builder.Services.AddTransient<IGetPrincipalUser, GetPrincipalUser>();
builder.Services.AddTransient<IAuthorizationHandler, ProhibitAdminAccountActionHandler>();
var app = builder.Build();
Also I have UseAuthentication and UseAuthorization as middleware. And my endpoints are not special in any way - they are decorated with Roles and custom policies. I even tried to add custom ClaimsIdentity in one of my endpoints to explicitly add Cookie identity to the logged user. Still - nothing. I ONLY use cookie auth, nothing else. And I can see this topic is quite popular...
What am I still doing wrong?
Update: The problem is fixed, partially. I just added to the current configuration the following code:
config.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = (int)403;
return Task.CompletedTask;
};
Now the **Unauthorized ** users receive the proper Forbidden status code. Still Unauthenticated usage of endpoints trigger the 404 response, which of course is unwanted.
Everything is fixed This is how I fixed it:
builder.Services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "Identity.Cookie";
config.LoginPath = "/User/Login";
config.LogoutPath = "/User/Logout";
config.AccessDeniedPath = "/User/Login";
config.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = (int)403;
return Task.CompletedTask;
};
config.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = (int)401;
return Task.CompletedTask;
};
});
Everything is working now.