Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 256
gyurisc Avatar asked Nov 23 '18 07:11

gyurisc


People also ask

How do I redirect after authentication?

The most common ways to implement redirection logic after login are: using HTTP Referer header. saving the original request in the session. appending original URL to the redirected login URL.

What is redirect authentication?

Redirect authenticationThe user or system is redirected to Okta for credential verification and is then provided authenticated access to the client application and other Service Providers. When a user signs in to the client application, they are redirected to Okta using a protocol like SAML or OpenID Connect (OIDC).

What is redirect URI in Auth0?

During a user's authentication, the redirect_uri request parameter is used as a callback URL. This is where your application receives and processes the response from Auth0, and is often the URL to which users are redirected once the authentication is complete.


2 Answers

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.

like image 111
Kirk Larkin Avatar answered Oct 02 '22 16:10

Kirk Larkin


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. enter image description here

like image 27
Edmund P Charumbira Avatar answered Oct 02 '22 15:10

Edmund P Charumbira