59

Using Forms Authentication in ASP.NET MVC when trying to log back into a site, it puts a ReturnUrl parameter in the query string. My Logon action method accepts a "returnUrl" string. However it seems that returnUrl string is always null, even when it is clearly in the query string. Any thoughts on why this might be the case or a possible fix?

1
  • 1
    This is actually by design. The returnUrl parameter is only populated automatically when you try to access an authorized resource but were not authenticated or authorized. see github.com/aspnet/Templates/issues/420 You can use @davewasthere approach (or with a Tag Helper) if you want returnUrl set for non-auth cases. Commented Mar 3, 2016 at 20:01

5 Answers 5

124

This tends to happen when you're using one generic logon form, but you're explicitly specifying the Controller and ActionMethod (which is causing a form post, but losing the querystring)

Just to clarify, this is what your code should look like in your BeginForm:

Html.BeginForm("LogOn", "Account", new { ReturnUrl = Request.QueryString["ReturnUrl"] })

EDIT: This is by design as RickAnd mentions in comments below. However it doesn't allow for the UI pattern of being deep in a site, clicking on LogOn, then returning to the page you were previously on, if it allows anonymous users. It's a commonly requested pattern. David Allen's approach to LogOff would also work nicely for a clean redirect at LogOn.

Sign up to request clarification or add additional context in comments.

7 Comments

It think this is really broken on the standard ASP.NET MVC templates. The code above just works! Thanks dave. :)
Yes - it's broken in the new MVC 4 template. To reproduce the error, simply create a new ASP.NET MVC 4 Web Application and add the [Authorize] attribute to the About Action. The returnUrl parameter will always be null unless it's added as @davewasthere specifies.
If you can't edit the BeginForm for some reason (i.e. when using a single form for your whole page), you can also put the following hidden field on your LogOn page: <input type="hidden" name="ReturnUrl" value="@Request.QueryString["ReturnUrl"]" />
@Catch22: Is there a bug report in for this? Quite frustrating when you're trying to learn MVC and it doesn't work correctly out of the box.
@SimonW's answer works better for me when using mixed GET/POST controllers. Entering ReturnUrl in BeginForm's 3rd parameter did not work for me when using POST.
|
28

Maybe you don't include the ReturnURL parameter into you login form's action attribute, thus posting to a URL without that parameter?

8 Comments

Not sure I follow. Are you saying you think I am not including the ReturnURL param in the action on the controller?
No I meant the HTML form tag's action attribute. You may have ReturnURL querystring on the login page. But if the login form posts it without that querystring parameter, then it's lost.
Hmm... No where in the code am explicitly setting the ReturnURL on any form post. It is automatically generated in the Query string by the framework.
Set a breakpoint inside the action method in question, then examine the Request.RawUrl property. There is probably no ?ReturnUrl=... query string segment, indicating that your form is posting to <form action="/Account/Login" /> rather than <form action="/Account/Login?ReturnUrl=..." />.
@Jon, Like this : <form action="your-url?ReturnUrl=<%=Request.QueryString["ReturnUrl"]%>" method="post">
|
7

Basically, The Asp.net MVC has some hidden features. For Example when you pass variable 'id' to controller action, it interprets 'id' as default identifier and puts it on browser query with fore slash.By using another name instead of 'id' we will see '?' rather than fore slash. Because of setting the 'id' name on RegisterRoutes method on global.asax file.

In this Problem you have created a custom data passer to controller by using this code:

using(Html.BeginForm("LogOn", "Account", FormMethod.Post))
{
//form fields
}

So Asp.net MVC ignores other useful data to pass to controller action, and we'll see returnUrl always null.

While, by using this, Asp.net MVC acts Correctly and returnUrl is mounted:

using(Html.BeginForm())
{
//form fields in LogOn View
}

By the way, When we use custom data passer to controller action, must pass another data manually like this:

using(Html.BeginForm("LogOn", "Account", new {ReturnUrl = Request.QueryString["ReturnUrl"] }))
{
//form fields
}

Comments

4

There are two ways I can think of to deal with logon and logoff scenarios. Dave Beer outlined one way, above. There is another approach that works in many situations. I used it when I coded the NerdDinner tutorial. The tutorial provides us with a logoff function that logs you off and takes you home. I did not want that. I wanted to return to the page I was on before I logged off. So I modified my Account controller logoff action to look like this

   public ActionResult LogOff()
    {
        FormsService.SignOut();
        return Redirect(Request.UrlReferrer.ToString());
    }

You can get fancier and pass in a returnUrl and test for it, in case you want to override this behavior. But I don't need that. This achieves the desired result. The Logon can work similarly. Maybe there are ways to use the MVC framework to do this for me, but until I learn them, this is VERY simple and works reliably.

2 Comments

Yeah, I really like this. As @rickand-msft says in a comment below my post, it's by design. We're changing that user-flow somewhat, but it is a fairly common pattern to return a user to the page they logged in from, even though it's not decorated with the Authorize attribute.
This is the only way to solve reflection (portswigger.net/kb/issues/…)
1

Try the following:

        public static MvcForm BeginForm(this HtmlHelper htmlHelper, string id)
    {
        string formAction = htmlHelper.ViewContext.HttpContext.Request.RawUrl;

        TagBuilder tagBuilder = new TagBuilder("form");

        tagBuilder.MergeAttribute("id", id);
        tagBuilder.MergeAttribute("action", formAction);
        tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(FormMethod.Post), true);

        HttpResponseBase httpResponse = htmlHelper.ViewContext.HttpContext.Response;
        httpResponse.Write(tagBuilder.ToString(TagRenderMode.StartTag));

        return new MvcForm(htmlHelper.ViewContext.HttpContext.Response);
    }

First ensure you have set the login url in the web.config, Next, ensure your Signin Form does not contain anything like action, for example:

View:

If you specify action you will always get null for return url:

Controller:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult SignIn(string userName, string password, bool? rememberMe, string returnUrl)
{
}

1 Comment

Ok so how do I set set the Id of the form without specifying an Action? This is what my Html.BeginForm looks like. <%using (Html.BeginForm("Logon", "Account", null, FormMethod.Post, new { id = "logonForm" })) {%>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.