Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReturnUrl Points to an ActionResult

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
like image 969
usefulBee Avatar asked Oct 17 '22 04:10

usefulBee


1 Answers

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.

like image 54
Amirhossein Mehrvarzi Avatar answered Oct 21 '22 04:10

Amirhossein Mehrvarzi