简体   繁体   中英

Authorized page redirects back to login after successful login

I have a simple asp.net-core app using version 2.1. The HomeController has a page with the authorized attribute. When I click on the About page that needs authorization I get to the Login page and after typing my username and password the following happens:

  • the user is successfully logged in
  • the user is redirected to /Home/About
  • HomeController.About method is hit in the debugger and the About view is served.
  • Somehow the user is redirected back to the AccountController.Login
  • the user is logged in so I can navigate to any page now that needs authorization.

I tried this with Chrome and Edge as well. I can reproduce the error with both browser.

I created a small repro project that reproduces the issue on my machine and setup.

Portfolio_Authentication

The steps I am using to reproduce the problem are the following:

  1. Register a user on the website
  2. Log out the user if it is logged in.
  3. Click on the About menu link in the header.
  4. Type in the username and password
  5. Notice the authentication is ok as the user name can be see top right side of the screen but the login does not redirect to the About page.

I am wondering why this is happening and how to correct this? Thanks for helping me. All feedback is welcome.

HomeController:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    [Authorize]
    public IActionResult About()
    {
        ViewData["Message"] = "Your application description page.";

        return View();
    } 
}

My Startup.cs looks the following:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        // services.AddAuthentication();
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    options.LoginPath = "/Account/LogIn";
                    options.LogoutPath = "/Account/LogOff";
                });

        // Add application services.
        services.AddTransient<IEmailSender, EmailSender>();

        services.AddMvc()
                .AddFeatureFolders(); // .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); ;
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseBrowserLink();
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseStaticFiles();

        app.UseAuthentication();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

and this is the log I see in my output window in Visual Studio:

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (1ms) [Parameters=[@__normalizedUserName_0='?' (Size = 256)], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [u].[Id], [u].[AccessFailedCount], [u].[ConcurrencyStamp], [u].[Email], [u].[EmailConfirmed], [u].[LockoutEnabled], [u].[LockoutEnd], [u].[NormalizedEmail], [u].[NormalizedUserName], [u].[PasswordHash], [u].[PhoneNumber], [u].[PhoneNumberConfirmed], [u].[SecurityStamp], [u].[TwoFactorEnabled], [u].[UserName]
FROM [AspNetUsers] AS [u]
WHERE [u].[NormalizedUserName] = @__normalizedUserName_0
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (2ms) [Parameters=[@__user_Id_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
SELECT [uc].[Id], [uc].[ClaimType], [uc].[ClaimValue], [uc].[UserId]
FROM [AspNetUserClaims] AS [uc]
WHERE [uc].[UserId] = @__user_Id_0
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (1ms) [Parameters=[@__userId_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
SELECT [role].[Name]
FROM [AspNetUserRoles] AS [userRole]
INNER JOIN [AspNetRoles] AS [role] ON [userRole].[RoleId] = [role].[Id]
WHERE [userRole].[UserId] = @__userId_0
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: Identity.Application signed in.
Portfolio.Features.Account.AccountController:Information: User logged in.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action method Portfolio.Features.Account.AccountController.Login (Portfolio), returned result Microsoft.AspNetCore.Mvc.RedirectResult in 32.6215ms.
Microsoft.AspNetCore.Mvc.Infrastructure.RedirectResultExecutor:Information: Executing RedirectResult, redirecting to /Home/About.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action Portfolio.Features.Account.AccountController.Login (Portfolio) in 41.5571ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 49.3022ms 302 
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:44392/Home/About  
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "About", controller = "Home"}. Executing action Portfolio.Features.Home.HomeController.About (Portfolio)
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization was successful.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method Portfolio.Features.Home.HomeController.About (Portfolio) - Validation state: Valid
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action method Portfolio.Features.Home.HomeController.About (Portfolio), returned result Microsoft.AspNetCore.Mvc.ViewResult in 2212.7896ms.
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor:Information: Executing ViewResult, running view About.
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor:Information: Executed ViewResult - view About executed in 3.1424ms.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action Portfolio.Features.Home.HomeController.About (Portfolio) in 2225.297ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 2233.0907ms 200 text/html; charset=utf-8
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:44392/Account/Login?ReturnUrl=%2FHome%2FAbout  
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Login", controller = "Account"}. Executing action Portfolio.Features.Account.AccountController.Login (Portfolio)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method Portfolio.Features.Account.AccountController.Login (Portfolio) with arguments (/Home/About) - Validation state: Valid
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: Identity.External signed out.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action method Portfolio.Features.Account.AccountController.Login (Portfolio), returned result Microsoft.AspNetCore.Mvc.ViewResult in 1528.1878ms.
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor:Information: Executing ViewResult, running view Login.
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor:Information: Executed ViewResult - view Login executed in 5.8984ms.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action Portfolio.Features.Account.AccountController.Login (Portfolio) in 1543.8386ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 1553.3133ms 200 text/html; charset=utf-8
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:44392/lib/bootstrap/dist/css/bootstrap.css  
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:44392/css/site.css  
Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware:Information: Sending file. Request path: '/css/site.css'. Physical path: 'C:\dev\web\portfolio-variants\Portfolio_Controller_V2\Portfolio\wwwroot\css\site.css'
Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware:Information: Sending file. Request path: '/lib/bootstrap/dist/css/bootstrap.css'. Physical path: 'C:\dev\web\portfolio-variants\Portfolio_Controller_V2\Portfolio\wwwroot\lib\bootstrap\dist\css\bootstrap.css'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 28.4192ms 200 text/css
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 41.4384ms 200 text/css

In your Github project, you have a site.js file that contains (amongst other things), the following jQuery event-handler:

$('form[method=post]').not('.no-ajax').on('submit', function () {
    ...

    $.ajax({
        url: $this.attr('action'),
        ...
        statusCode: {
            200: redirect
        },
        ...
    }).error(highlightErrors);

    return false;
}

When you submit your login form, you end up running through this block of code above, which then invokes your redirect callback function for a statusCode of 200 , shown below:

var redirect = function (data) {
    if (data.redirect) {
        window.location = data.redirect;
    } else {
        window.scrollTo(0, 0);
        window.location.reload();
    }
};

In the scenario you've described, data.redirect is undefined . In that case, you end up calling window.location.reload() , which, of course, reloads the login page and explains the issue you've been having clearly.

Here's a step-by-step breakdown of what happens:

  1. The submit event is triggered when clicking "Log In".
  2. The browser-based POST is intercepted, being sent instead as an XHR request.
  3. The server logs in the user, assigns the cookie and returns a 302 response for redirecting to /Home/About .
  4. The XHR internal mechanics follows the redirect and pulls down the HTML for the /Home/About page.
  5. Your Javascript redirect callback is invoked where data represents the response to the /Home/About page (the text/html response).
  6. Finally, whilst still on the /Account/Login page , the page is reloaded as described above.

Due to how you've set up the jQuery selector shown in the first code snippet, you can simply add the no-ajax class to your login form and it will behave as expected.

For anyone who might be having the same problem, for me, options.ExpireTimeSpan was set to 5 seconds. Changing to something like 4 hours fixed the problem. 在此处输入图片说明

You should also call services.AddAuthentication in the ConfigureServices method in you start-up file.

The call to AddAuthentication should be configured to your application needs. (Cookie, External Logins, etc.)

Even though you are not migrating from 1.x to 2.x the following article I find it very useful: Migrate authentication and Identity to ASP.NET Core 2.0 .

You haven't used this middleware in Configure method of startup class.

app.UseAuthorization();

Place this middleware after app.UseAuthentication();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM