5
votes

Here is how the scenario goes:

  • Start an MVC project from scratch
  • Test Controller decorated with [Authorize] attribute
  • User Logs in and directed to Home
  • User clicks a link that redirects to the TestController's Index method
  • User waits 60 seconds for the Forms Authentication to timeout
  • User clicks a link that calls an ActionMethod residing on the TestController
  • The MVC framework redirects user to Login page and attaches the ActionMethod name to the URL instead of attaching the Index Action Method

TestController:

[Authorize]
public class TestController : Controller
{
    // GET: Test
    public ViewResult Index()
    {
        return View();
    }

    [ValidateInput(false)]
    public ActionResult ActionTest()
    {
        return new EmptyResult();
    }
}

HomeController:

[Authorize]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

AccountController:

public class AccountController : Controller
{
    [AllowAnonymous]
    public ActionResult Login()
    {
        return View();
    }

    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public ActionResult Login(LoginViewModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            try
            {
                FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);

                if (Url.IsLocalUrl(returnUrl))
                {
                    return Redirect(returnUrl);
                }
                else
                    return RedirectToAction(controllerName: "Home", actionName: "Index");
            }
            catch
            {
                return View(model);
            }
        }
        return View(model);
    }
}

Login.chtml

@model TestLoginProject.Models.LoginViewModel

@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
  .....................
</head>

<body>
    <div class="container">
        @using (@Html.BeginForm("Login", "Account", new { returnUrl = Request.QueryString["ReturnUrl"] }, FormMethod.Post, new { @class = "form-signin" }))
        {
            @Html.AntiForgeryToken()
            ....................
            ....................
        }
    </div>
</body>
</html>

Web Config

<authentication mode="Forms">
  <forms loginUrl="~/Account/Login" timeout="1" />
</authentication>

The expectation of the return url is:

http://localhost:2441/Account/Login?ReturnUrl=%2fTest%2fIndex

Instead, the current value is:

http://localhost:2441/Account/Login?ReturnUrl=%2fTest%2fActionTest

Notes:

  • When a user clicks the link after timeout, no Test Actions are hit before the redirection to the Login page takes place
  • All routes are the default as provided when starting an Empty MVC project from scratch in VS2017
1
share your routing and your view page. Also put a breakpoint in the actions (or log) to see which action is called and in what order if more than one is - Yishai Galatzer
@YishaiGalatzer, after the Forms Authentication times out, and I attempt to login again, only the SomePagePartialView Action is hit. - usefulBee
@YishaiGalatzer, the view contains pretty much only the following relevant line: @Html.Action("SomePagePartialView") - usefulBee
URLs are typically generated by routes. You haven't posted a single route in your question. - NightOwl888
Either something is wrong in your routing table (which I doubt, but you should check), or you login is sending you to the partialview instead of the view (debug that). Last your main view is sending you to the partial view as well with the Action("SomePagePartialView"), for the sake of being 100% sure, make that view just return something else for now (and same with the PartialViewPage) - Yishai Galatzer

1 Answers

3
votes

This is a normal behavior that you mentioned!

The MVC framework redirects user to Login page and attaches the ActionMethod name to the URL instead of attaching the Index Action Method

Many thanks to MVC Security pipeline. When you use forms authentication and the user is not authenticated or authorized, the ASP.NET security pipeline redirects to the login page and passes returnUrl as a parameter equal to the page that redirected to the login page (here is the controller action which requires authorization which you called by clicking on a link).

So here you can't expect index (currently loaded page with no valid and persistent authentication) and subsequently the ActionMethod calls security pipeline and the returnurl is enumerated just in time.

Note that this is because of Synchronized communication between Controller and View.