Asp.net-mvc – What initially sets the ReturnUrl parameter when using AuthorizeAttribute

asp.net-mvc

In an ASP.NET MVC project, when you decorate a class or method with [Authorize] and authorization fails, the site automatically redirects to the login page (using the loginUrl specified in web.config). In addition, something in the ASP.NET MVC framework passes along the original request's URL as a ReturnUrl parameter.

What is responsible for appending this ReturnUrl? I couldn't find any code for it in the project template. I also took a look at the code for AuthorizeAttribute in the ASP.NET stack source code but couldn't find anything there. I also tried searching the entire ASP.NET stack source code for "returnurl" but couldn't locate anything.

The reason I ask is that I've discovered a bug in this process. You can see this with a brand new Internet ASP.NET MVC project. Set the FormsAuth timeout to 1 minute in the web.config and then sign in. Wait over a minute and try to sign out. This will redirect to the login page with a ReturnUrl of /account/logoff, which leads to a 404 after logging in. I've worked around this for now with my own AuthorizeAttribute:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);

        if (filterContext.Result is HttpUnauthorizedResult)
        {
            string returnUrl = null;
            if (filterContext.HttpContext.Request.HttpMethod.Equals("GET", System.StringComparison.CurrentCultureIgnoreCase))
                returnUrl = filterContext.HttpContext.Request.RawUrl;

            filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary()
            {
                { "client", filterContext.RouteData.Values[ "client" ] },
                { "controller", "Account" },
                { "action", "Login" },
                { "ReturnUrl", returnUrl }
            });
        }
    }
}

However, I would like to take a look at the source and see if I can figure out why this bug exists, if it is indeed a bug.

Best Answer

The returnUrl querystring parameter is added to the redirect to the login page inside the FormsAuthentication class in the System.Web.dll assembly. FormsAuthenticion.RedirectToLoginPage method overloads end up calling the internal method, GetLoginPage. Both the name of the "ReturnUrl" variable and the LoginUrl can be overridden via web.config settings.

When the default AuthorizeAttribute encounters an unauthorized request, it just returns an HttpUnauthorizedResult, which is just a wrapper around the HttpStatusCodeResult with a status code of 401. The FormsAuthenticationModule kicks in behind the scenes and does the rest of the work. There is no direct interaction between MVC and these base classes, unless of course you are calling the FormsAuthentication class static methods directly.

Your solution is a standard one, when you want to override this behavior.

The GetLoginPage method that does the work is as follows:

internal static string GetLoginPage(string extraQueryString, bool reuseReturnUrl)
{
    HttpContext current = HttpContext.Current;
    string loginUrl = FormsAuthentication.LoginUrl;
    if (loginUrl.IndexOf('?') >= 0)
    {
        loginUrl = FormsAuthentication.RemoveQueryStringVariableFromUrl(loginUrl, FormsAuthentication.ReturnUrlVar);
    }
    int num = loginUrl.IndexOf('?');
    if (num >= 0)
    {
        if (num < loginUrl.Length - 1)
        {
            loginUrl = string.Concat(loginUrl, "&");
        }
    }
    else
    {
        loginUrl = string.Concat(loginUrl, "?");
    }
    string str = null;
    if (reuseReturnUrl)
    {
        str = HttpUtility.UrlEncode(FormsAuthentication.GetReturnUrl(false), current.Request.QueryStringEncoding);
    }
    if (str == null)
    {
        str = HttpUtility.UrlEncode(current.Request.RawUrl, current.Request.ContentEncoding);
    }
    loginUrl = string.Concat(loginUrl, FormsAuthentication.ReturnUrlVar, "=", str);
    if (!string.IsNullOrEmpty(extraQueryString))
    {
        loginUrl = string.Concat(loginUrl, "&", extraQueryString);
    }
    return loginUrl;
}