2

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.

Andrеw
  • 81
  • 7
  • 1
    After " ' config.LoginPath = "/User/Login";" ,"I configure the AccessDeniedPath = "/User/Login"" Why you do the AccessDeniedPath to Login view again ? You should create a view , and use " config.AccessDeniedPath = "/xx/xxx" ", make sure the url is correct. – Qing Guo Sep 01 '22 at 03:38
  • What's your url ? – Qing Guo Sep 01 '22 at 09:43
  • The app is not deployed. I fixed it. Check the original answer. – Andrеw Sep 01 '22 at 10:06

0 Answers0