Question

I am writing an ASP.NET MVC 2.0 application which requires users to log in before placing a bid on an item. I am using an actionfilter to ensure that the user is logged in and, if not, send them to a login page and set the return url. Below is the code i use in my action filter.

if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
    filterContext.Result = new RedirectResult(String.Concat("~/Account/LogOn","?ReturnUrl=",filterContext.HttpContext.Request.RawUrl));
    return;
}

In my logon controller I validate the users credentials then sign them in and redirect to the return url

FormsAuth.SignIn(userName, rememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
    return Redirect(returnUrl);
}

My problem is that this will always use a Get (HttpGet) request whereas my original submission was a post (HttpPost) and should always be a post. Can anyone suggest a way of passing this URL including the HttpMethod or any workaround to ensure that the correct HttpMethod is used?

Was it helpful?

Solution

There's no easy way to do this. What I would recommend you is to redirect the unauthenticated users to the login page not when posting to some URL but when requesting the form that will POST to the authenticated URL.

If you know that the form you are presenting to an unauthenticated user will POST to an authenticated part of the site, well, don't present him the form. When this form is requested simply redirect to the login page for authentication and once authenticated redirect to the original form. This way you will ensure that only authenticated users will POST to the protected resource.

As far as automated POST requests are concerned (bots, web services, ...) returning a simple 401 status code to requests that do not provide credentials should be more than sufficient.

OTHER TIPS

I think I get why you want the authentication to only be on the bid POST action. A bid requires login, but any non-logged in user can see the auction page. Just like ebay/amazon etc. Everything is visible until you require payment or action based on a user.

You could change your attribute to instead return the Request.UrlReferrer to the login page if the Request.RequestType is a POST. Then they would be redirected to the auction page and can click bid again once they are logged in. You could even pass along a certain field, say amount, with the UrlReferrer so that you could re-populate the amount field once they land on the auction page. You could get that field from the Request.Form collection.

// in usage...    
[RequireLogin(AdditionalFields="amount,someotherfield")]
[HttpPost]
public ActionResult Bid(.....)

// the attribute 
class RequireLoginAttribute : ActionFilterAttribute
{
    public string AdditionalFields { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            var returnUrl = filterContext.HttpContext.Request.RawUrl;
            if (filterContext.HttpContext.Request.RequestType == "POST")
            {
                returnUrl = filterContext.HttpContext.Request.UrlReferrer.PathAndQuery;
                // look for FORM values in request to append to the returnUrl
                // this can be helpful for a good user experience (remembering checkboxes/text fields etc)
            }

            filterContext.Result = new RedirectResult(String.Concat("~/Account/LogOn", "?ReturnUrl=", returnUrl));
            return;
        }
        base.OnActionExecuting(filterContext);
    }
}

You can write two Controller methods with the same name, but one for get and another for post, and remember the ReturnUrl in the get method in TempData(or session), and then get the ReturnUrl from TempData when post request arrives:

The code may looks like this:

    public ActionResult LogOn(string returnUrl)
    {
        if (!string.IsNullOrEmpty(returnUrl))
        {
            TempData["ReturnUrl"] = returnUrl;
        }
        return View();
    }

    [HttpPost]
    public ActionResult LogOn(LogOnModel model, FormCollection collecton)
    {
        if (ModelState.IsValid)
        {
            AuthenticationResult logonStatus = TransactionScriptFactory.GetTransactionScript<UserTransactionScripts>()
                                                                       .LogOn(model.Email, model.Password);

            if (logonStatus.AuthResult == AuthResultEnum.Success)
            {
                FormsService.SignIn(logonStatus.User.UserId, logonStatus.User.NickName, false);

                object returnUrl = string.Empty;
                TempData.TryGetValue("ReturnUrl", out returnUrl);
                string returnUrlStr = returnUrl as string;
                if (!string.IsNullOrEmpty(returnUrlStr))
                {
                    return Redirect(returnUrlStr);
                }
                else
                {
                    return RedirectToAction("Index", "Home");
                }
            }

......

It is defintely when you first arrives to the page by using get action, then post data to server.

I think you can also get the whole url from Request.UrlReferrer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top