Redirecting after login results in 404 error if the user submits the form to the POST-only action and their authentication is complete

I have a MVC application using forms authentication and I get 404 errors. What happens is that the user submits the form to the POST action only after the expiration of their authentication and is redirected to the login page. After logging in, they are redirected back to the original URL using GET, which will result in a 404 error, because the action is performed only by POST.

I have two questions:

  • My idea of ​​getting around this is to somehow determine if the redirected action is only valid POST action and is instead redirected to the main page. How can i do this?

  • Ideally, the application will remember the posted values ​​and send them to the source URL via POST, but I have no idea how to bypass forms authentication to do this, and I suspect it will be open to security vulnerabilities. This is a good idea, and if so, how can this be done?

+4
source share
5 answers

I created an action filter as a result of the answers above, I will leave it here for posterity. It passes any given parameters from the action taken 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 HttpPost the appropriate action using HttpPostOrRedirect , as follows:

 [HttpPostOrRedirect("Display", "User", new[] { "id", "param1", "param2" })] public ActionResult Delete(User user, int param1, string param2) { ... } 
+3
source

A simple fix would be to create only a GET action with the same name as POST, only to redirect to the main page. Creating a solution that resumes writing a form after logging in will be quite a lot of work for minimal payoff.

UPDATE:

Regarding the amount of work, all of these GET activities would be created. A more elegant option would be to create an attribute specifically for this scenario, something like HttpPostOrRedirectAttribute , which you could use only actions to decorate these messages, rather than using HttpPostAttribute . The behavior of this would be that it accepts messages, but instead of throwing 404, it redirects to other verbs.

+2
source

You are faced with exactly this problem, we create [HttpGet] actions for messages redirected to the Index; the user loses the entered data - so this is not very good, but it was a quick way to get around it for us.

+1
source

How to make a GET action redirecting a page containing the original form?

You will add additional information to the ModelView data to indicate that the message should be displayed to the user. This post should say something like this ...

"You submitted this form during logout - now that you are logged in, you want to continue submitting"

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

As you say, this is quite a bit of extra work if you have many form pages, but there must be some way to encapsulate behavior for reuse.

+1
source

Here's an improved version of @stusherwin's answer with support for ValidateAntiForgeryToken and MVC realms .

Your POST actions probably have a ValidateAntiForgeryToken attribute to prevent CSRF attacks. In this case, the ValidateAntiForgeryToken filter will always be executed first, since it is an authorization filter. Therefore, we need to make the HttpPostOrRedirectAttribute an authorization filter. Otherwise, an exception will be thrown that the anti-fake token was not found.

Another improvement is the addition of MVC redirection

  public class HttpPostOrRedirectAttribute : FilterAttribute, IAuthorizationFilter { public string RedirectAction { get; set; } public string RedirectController { get; set; } public string RedirectArea { 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 HttpPostOrRedirectAttribute(string redirectAction, string redirectController, string redirectArea) { RedirectAction = redirectAction; RedirectController = redirectController; RedirectArea = redirectArea; } public HttpPostOrRedirectAttribute(string redirectAction, string redirectController, string redirectArea, string[] parametersToPassWithRedirect) { RedirectAction = redirectAction; RedirectController = redirectController; RedirectArea = redirectArea; ParametersToPassWithRedirect = parametersToPassWithRedirect; } public void OnAuthorization(AuthorizationContext filterContext) { if (filterContext.HttpContext.Request.HttpMethod == "POST") return; 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]); } if (RedirectArea.IsNotEmpty()) routeValues.Add("area", RedirectArea); string controller = RedirectController ?? context.RouteData.Values["controller"].ToString(); UrlHelper urlHelper = new UrlHelper(context); return urlHelper.Action(RedirectAction, controller, routeValues); } } 

Here is an example of using it together with the ValidateAntiForgeryToken attribute and redirecting to the admin area:

 [HttpPostOrRedirect("Display", "User", "Admin", new[] { "id", "param1"}, Order = 0)] [ValidateAntiForgeryToken(Order = 1)] public ActionResult Delete(User user, int param1, string param2) { ... } 
+1
source

Source: https://habr.com/ru/post/1343743/


All Articles