Secured controller action shows 401 error instead of redirecting to login page in .NET 7

Jaime Stuardo 66 Reputation points
2023-06-02T23:25:25.28+00:00

I am developing a .NET 7 Web application that has secured areas.

This is a monolith application, so that it is separated in different assemblies. I have one host assembly (the entry assembly), called EntryPortal.Core. On the other hand, I have other assembly called Modules.Authenticate.Core.

The entry portal assembly has this code to load the authentication assembly:

builder.Services.AddModule<Modules.Authenticate.Core.Startup>("Authenticate", builder.Configuration);

var app = builder.Build();

app.UseRouting();

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

var modules = app.Services.GetRequiredService<IEnumerable<Module>>();
foreach (var module in modules)
{
    app.Map($"/{module.RoutePrefix}", ab =>
    {
        ab.UseRouting();
        module.Startup.Configure(ab);
    });
}

That way, I call the Startup class of every module.

So far, the assemblies separation works well, but now I am implementing secured pages.

This is the Startup class of the module:

public class Startup : IModuleStartup
{
    public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
    {
        services.AddTransient<IPasswordHasher<ApplicationUser>, CustomPasswordHasher>();
        services.AddTransient<IEmailSender, EmailSender>();
        services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
        {
            options.SignIn.RequireConfirmedEmail = true;
            options.Stores.MaxLengthForKeys = 128;
            options.User.RequireUniqueEmail = true;
        })
            .AddDefaultTokenProviders();

        services.AddTransient<IUserStore<ApplicationUser>, CustomUserStore>();
        services.AddTransient<IRoleStore<ApplicationRole>, CustomRoleStore>();

        services.AddScoped<IAccountService, AccountService>();
        services.AddScoped<IUsuarioService, UsuarioService>();
        services.AddDbContext<SecuWebModulesAuthenticateContext>(options =>
        {
            options
                .UseSqlServer(configuration.GetConnectionString("Modules.Authenticate"));
#if DEBUG
            options.LogTo(x => Debug.WriteLine(x));
#endif
        });

        // Agrega autenticación
        services.AddAuthentication()
            .AddCookie("Cookies", options =>
            {
                options.LoginPath = "/Security/Login";
                options.LogoutPath = "/Security/Logout";
                options.AccessDeniedPath = "/Security/AccessDenied";
                options.ReturnUrlParameter = "ReturnUrl";
            })
            .AddJwtBearer(x =>
            {
                x.RequireHttpsMetadata = true;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = configuration["Modules:Authenticate:AuthJwt:Issuer"],
                    ValidateAudience = true,
                    ValidAudience = configuration["Modules:Authenticate:AuthJwt:Audience"],
                    ValidateIssuerSigningKey = true,
                    RequireExpirationTime = false,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.Zero,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Modules:Authenticate:AuthJwt:Key"] ?? string.Empty))
                };
            });

        services.AddAuthorization();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

On the other hand, I have this controller:

[Authorize]
public class UsuarioController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

Well, when I browse to https://localhost:7255/Usuario, this page is shown:

User's image

How can I solve this so that a redirect to the login page occurs? As you see, I have a options.LoginPath = "/Security/Login" definition.

Thanks

Jaime

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,400 questions
0 comments No comments
{count} votes

3 answers

Sort by: Most helpful
  1. Zhi Lv - MSFT 32,106 Reputation points Microsoft Vendor
    2023-06-05T06:03:29.2266667+00:00

    Hi @Jaime Stuardo

    In the Configure method, you can use StatusCodePages middleware to capture the request, if the status code is 401, redirect to the login page:

    app.UseStatusCodePages(async context => {
        var request = context.HttpContext.Request;
        var response = context.HttpContext.Response;
        //using System.Net;
        if (response.StatusCode == (int)HttpStatusCode.Unauthorized)
        // you may also check requests path to do this only for specific methods       
        // && request.Path.Value.StartsWith("/specificPath")
    
        {
            response.Redirect("api/User/Login");  //redirect to the login page.
        }
        });
    app.UseAuthentication();
    app.UseAuthorization();
    

    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,

    Dillion

    1 person found this answer helpful.

  2. Bruce (SqlWork.com) 61,491 Reputation points
    2023-06-04T17:01:33.33+00:00

    is your security controller login action defined? Show your code.

    0 comments No comments

  3. Bruce (SqlWork.com) 61,491 Reputation points
    2024-07-08T16:11:53.2966667+00:00

    your issue is your app has two authentication schemes. jwt returns a 401 on unauthorized, and cookie returns a redirect to login page. your code needs to define which scheme to use for each endpoint (it is now defaulting to jwt). see:

    https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-8.0

    0 comments No comments