Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redirect after login results in 404 error if a user submits a form to a POST-only action and their authentication has timed out

I have an MVC app using Forms Authentication, and I'm getting 404 errors. What's happening is the user happens to be submitting a form to a POST-only action when their authentication has timed out, and they are redirected to the login page. After login they are redirected back to the original URL using GET, which will result in a 404 error as the action is POST-only.

I have two questions:

  1. My idea to get around this is to somehow detect whether the action being redirected to is a POST-only action, and redirect to the homepage instead. How would I go about doing that?

  2. Ideally, the app would remember the values posted, and submit them to the original URL via POST, but I have no idea how I would bypass Forms Authentication to do this, and I suspect this would be open to security vulnerabilities. Is this a good idea and if so how could it be done?

like image 246
stusherwin Avatar asked Mar 15 '11 09:03

stusherwin


4 Answers

A simple fix would be to create a GET only action with the same name as the POST only one, that justs redirects to the homepage. Creating a solution that will resume a form post after a log in would be quite a lot of work for minimal gain.

UPDATE:

Regarding the amount of work it would be to create all of these GET actions.. A more elegant option would be to create an attribute specifically for this scenario, something named like HttpPostOrRedirectAttribute, that you could use to decorate these post only actions rather than use the HttpPostAttribute. The behaviour of this would be that it accepts posts, but rather than throwing 404 performs redirects for other verbs.

like image 136
UpTheCreek Avatar answered Oct 27 '22 16:10

UpTheCreek


I created an action filter as a result of the responses above, I'll leave it here for posterity. It passes any specified parameters from the attempted action through to the redirect action.

public class HttpPostOrRedirectAttribute : ActionFilterAttribute
{
    public string RedirectAction { get; set; }
    public string RedirectController { get; set; }
    public string[] ParametersToPassWithRedirect { get; set; }

    public HttpPostOrRedirectAttribute(string redirectAction)
        : this(redirectAction, null, new string[] { })
    {
    }

    public HttpPostOrRedirectAttribute(string redirectAction, string[] parametersToPassWithRedirect)
        : this(redirectAction, null, parametersToPassWithRedirect)
    {
    }

    public HttpPostOrRedirectAttribute(string redirectAction, string redirectController, string[] parametersToPassWithRedirect)
    {
        RedirectAction = redirectAction;
        RedirectController = redirectController;
        ParametersToPassWithRedirect = parametersToPassWithRedirect;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.HttpMethod == "POST")
        {
            base.OnActionExecuting(filterContext);
        }
        else
        {
            string redirectUrl = GetRedirectUrl(filterContext.RequestContext);
            filterContext.Controller.TempData["Warning"] = "Your action could not be completed as your"
                + " session had expired.  Please try again."; 
            filterContext.Result = new RedirectResult(redirectUrl);               
        }
    }

    public string GetRedirectUrl(RequestContext context)
    {
        RouteValueDictionary routeValues = new RouteValueDictionary();
        foreach (string parameter in ParametersToPassWithRedirect)
        {
            if(context.RouteData.Values.ContainsKey(parameter))
                routeValues.Add(parameter, context.RouteData.Values[parameter]);
        }
        string controller = RedirectController 
            ?? context.RouteData.Values["controller"].ToString();
        UrlHelper urlHelper = new UrlHelper(context);
        return urlHelper.Action(RedirectAction, controller, routeValues);           
    }
}

To use, simply replace the HttpPost filter on the relevant action with HttpPostOrRedirect thus:

[HttpPostOrRedirect("Display", "User", new[] { "id", "param1", "param2" })]
public ActionResult Delete(User user, int param1, string param2)
{
    ...
}
like image 41
stusherwin Avatar answered Oct 27 '22 15:10

stusherwin


How about making a GET action that redirects to the page that contains the original form?

You coudl add some extra info to the ModelView data, to indicate that a message should be displayed to the user. This message should say something like this...

"You submitted this form whilst logged out - now that you've logged in, do you wish to continue the submission"

Or, if you really wanted to, the extra information in the ModelViewData could cause the form to be automatically submitted.

As you say, this is quite a bit of extra work, if you have lots of form pages, but there ought to be some way of encapsulating the behaviour for re-use.

like image 43
belugabob Avatar answered Oct 27 '22 15:10

belugabob


Having encountered exactly this problem we create [HttpGet] actions for the Posts that redirected to the Index; the user loses their entered data - so it isn't great - but it was the quick way round it for us.

like image 40
Ciaran Avatar answered Oct 27 '22 15:10

Ciaran