Is it possible to block an action to be executed directly from the browser, but still be able to redirect to it from inside the site? I know it sounds stupid but this is what I want:
Let's say there are two routes, Index and Index2 of HomeController.
www.website.com/home will cause Index to be executed, I then want to execute some code and then redirect (or server transfer) to www.website.com/home/index2.
I don't want the URL www.website.com/home/index2 to be accessed directly by the user, without going to home/index first.
Is there something I can do to deactivate/activate the route at runtime?
EDIT: Sorry, this question is titled "Session Fixation" because I need Index to run prior to Index2 to ensure we are killing all Sessions in Index and renewing them before Index2 executes.
I cannot kill the session and execute the code in Index2 in the same request since Index2 needs session to do its job.
Not if you do a redirect. If you do a redirect it will appear to the controller the same way as if the request simply originated at the browser (which it does). You could gin something up with TempData -- i.e. set a flag there and only execute the action if the flag is in TempData -- but if it isn't the next request from the browser it will fail. That is, you may get another request, say via AJAX, that comes between the original and the redirect. The TempData would be wiped out by that request.
If, on the other hand, you simply want to execute the code, then don't expose Index2 as a public method and call it directly. If it's in the same controller, this should just work. You'll end up with the same URL, but if it's just getting the correct view that you want this should be ok.
If the user is redirected to www.website.com/home/index2, then they have to make the browser request even if it's automatically happening on their behalf. The only way to prevent that is to perform a check on the Index2 action that checks the HTTP Referer.
Another option would be to have Index return the ActionResult of Index2, that way the URL remains at /home/index, but the results of Index2 are returned and as far as the user is concerned they were never redirected.
You can put an ActionFilter and do your session cleanup / etc from there. This way your code will always be executed before Index2 - and before any action for which you put this attribute.
public class AbandonSessionAttribute: ActionFilterAttribute
{
public void OnActionExecuting(ActionFilterContext context)
{
if (Session.User.IsAuthenticated || Session.Count > 0) // <<-- this is the important line
{
Session.Clear();
FormsAuthentication.SignOut(); // and so on
context.Result = new RedirectResult("/Index2");
}
}
}
// now, if this is called with non-empty session, it will be cleared and user will be redirected again here
[AbandonSession]
public ActionResult Index2()
{
}
Notice the "this is the important line". There you need to detect if user is allowed to enter Index2. I do it by checking that Session is not empty. However, after the redirect MVC may add it's own Session variables, so Session won't ever be empty and the filter will redirect again and again... You'll need to find out what is OK to be presented in Session. From your question it's not clear (for me) if ANY navigation to Index2 should clear the session - i.e. during site browsing. If so, code above is OK, if not - it's up to you to decide how to detect this (for example, put something into session on some pages and remove from there on others).
Again, I don't see why Session won't work for you (and by the way TempData also works via Session). If the action filter you detect that request is not "clean" - there're login, session vars, etc - so you cleanup this and redirect once again to the same action - this causes new Session creation and so on. This time the filter doesn't find anything - since the Session has just been cleaned up - and so action is allowed to execute.
OK, this is what we ended up doing. Anyone see any caveats with this approach?
Index()
{
expire all session and forms-Authententication cookies
}
Index2()
{
if(Session.IsNewSession)
{
redirect to Index action
}
else
{
Show view
}
}
You could define routing:
routes.MapRoute(
"Index2Redirect",
"HomeController/Index2",
new { controller = "HomeController", action = "Index", id = "" }
);
EDIT:
No, it is not good.
Related
After a user has completed a form in MVC and the post action is underway, I am trying to redirect them back to another view within another model.
Eg. This form is a sub form of a main form and once the user has complete this sub form I want them to go back to the main form.
I thought the following might have done it, but it doesn't recognize the model...
//send back to edit page of the referral
return RedirectToAction("Edit", clientViewRecord.client);
Any suggestions are more that welcome...
You can't do it the way you are doing it. You are trying to pass a complex object in the url, and that just doesn't work. The best way to do this is using route values, but that requires you to build the route values specifically. Because of all this work, and the fact that the route values will be shown on the URL, you probably want this to be as simple a concise as possible. I suggest only passing the ID to the object, which you would then use to look up the object in the target action method.
For instance:
return RedirectToAction("Edit", new {id = clientViewRecord.client.ClientId});
The above assumes you at using standard MVC routing that takes an id parameter. and that client is a complex object and not just the id, in which case you'd just use id = clientViewRecord.client
A redirect is actually just a simple response. It has a status code (302 or 307 typically) and a Location response header that includes the URL you want to redirect to. Once the client receives this response, they will typically, then, request that URL via GET. Importantly, that's a brand new request, and the client will not include any data with it other than things that typically go along for the ride by default, like cookies.
Long and short, you cannot redirect with a "payload". That's just not how HTTP works. If you need the data after the redirect, you must persist it in some way, whether that be in a database or in the user's session.
If your intending to redirect to an action with the model. I could suggest using the tempdata to pass the model to the action method.
TempData["client"] = clientViewRecord.client;
return RedirectToAction("Edit");
public ActionResult Edit ()
{
if (TempData["client"] != null)
{
var client= TempData["client"] as Client ;
//to do...
}
}
Is there a way to restrict a controller action to be accessible only from a specific view? I have a Details page for a queried entity in my database and basically by a button and a simple JS confirmation prompt I would like to change few properties of this object and save it back to the database. I've developed a controller action method which does the job but I have no idea how to restrict access to it, so users cannot (un)intentionally modify entities by passing a specific url in the browser. I would like the action to be only accessible on this specific Details page, by pressing the designated button.
I tried using [ChildActionOnly] but it's only accessible from another action method, and not a view.
Thank you for your help.
Thanks to Stephen Muecke's comment I've managed to get it working.
The button is a simple Action Link, which redirects to the restricted controller action:
public ActionResult ReturnBook(int id)
{
if (Request.UrlReferrer == null)
{
return HttpNotFound();
}
//code
}
The method checks, if there is any UrlReferrer at all. Typing action url in the browser results in the referrer being null.
If the call is made by ActionLink from a View, the UrlReferrer is not null and optionally its property Request.UrlReferrer.AbsolutePath can be checked and compared to a desired url, from which the invoke can only be made. In my case, as I am not invoking this method anywhere else in my code, I stayed with just null/notnull condition.
I'm new to asp.net mvc, so please bear with me.
I'm using TempData when I redirect to another Action and I don't want to "dirty" the URL with information. For example:
[AllowAnonymous]
public ActionResult ConfirmationEmailSent()
{
if (TempData["Username"] != null)
{
ViewBag.Username = TempData["Username"];
return View("ConfirmationEmailSent");
}
return View("Error");
}
So far so good, the user gets a simple and innocent looking web page with some message containing his username. But if the user hits the 'refresh' button then he gets the "Error" view, because TempDate is unavailable.
I would like to have the ability to redirect to action with information not presented in the querystring and also that if the user hits the refresh button then he gets just the same page.
Any ideas how to do this? (without session)
Thank you.
What you need is a way to have information persist across more than one request in a way that is associated with the browser. QueryString and Session are your two best options.
If the view is the result of a form post, you could make it a hidden input, but the user will get a prompt when they refresh (do you want to resubmit form?), but this is not good, as you should be doing a Post/Redirect/Get (PRG).
Erick
It sounds like cookies would do what you want. Then just delete them when you're done (or don't set an expiration date on them, in which case the browser will delete them for you when the session completes).
The Goal is to redirect all users to my custom Security Page on each httpRequest as long as the Session variable is null or empty.
In Global.asax
The method causes an error as follows:
This webpage has a redirect loop
The webpage has resulted in too many redirects. Clearing your cookies for this site or allowing third-party cookies may fix the problem. If not, it is possibly a server configuration issue and not a problem with your computer.
protected void Application_PostAuthorizeRequest()
{
if ((Session["SecurityCodeApproved"] == null || !(bool)Session["SecurityCodeApproved"]))
{
Response.RedirectToRoute("Security");
}
}
It looks like you are redirecting to the same page, checking if the session cookie is set, and if not, redirecting it again. Its going to continuously redirect itself, until a session cookie is set, and you aren't setting one. You need to either redirect elsewhere or set the cookie to break the loop.
Do not do this in your Global.asax because as you have noticed, you will not always have a session every time that event fires. Also your code will fire on ALL requests, including those for resources like css, images, and JavaScript. What you want to do is use a Global Filter Attribute to perform your logic or if you are using WebForms, you want to do this at the Page Level.
When Filters are executed you are garaunteed a FilterContext that will have the request and the session objects you need.
You can then redirect to your security page, but make sure your filter attribute is ignored on your security point.
I'm working on a system that needs to know a user's choice before they enter a site. Up till now the choice has been stored in a cookie and checked by JavaScript on the page load - if the cookie doesn't exist then a dialog is shown and the user makes the choice.
Although we'd normally expect the user to arrive at the homepage of the application, they can legally follow a URL to any page within the application, so the JavaScript to check the choice exists on every page.
This has caused problems (almost always fixed by clearing cookies) so we're switching to store the choice in the database. What we need is a neat way of making sure that all pages (MVC and Web Forms) check that the choice has been made and, if it hasn't, either display a dialog or redirect to a page where the choice can be made.
The main thing bothering me is that to cause a redirect using MVC, I need to return a RedirectResult, and this can only be done from an Action. I don't want every action to have code regarding this check - it seems like the kind of thing that should be possible from a base controller (in the same way a base page could cause a Response.Redirect.
Can anyone suggest a good way for all pages to perform a check on the database and then either cause a redirect or show a dialog?
The main thing bothering me is that to cause a redirect using MVC, I
need to return a RedirectResult, and this can only be done from an
Action.
Oh not at all. You could also redirect from a custom action filters.
For example you could write a custom IAuthorizationFilter that will check whether the user made the necessary choice and if not redirect to some given page. The check could be done against a cookie, database or wherever you decide to persist this information:
public class EnsureChoiceHasBeenMadeAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// get the current user
var user = filterContext.HttpContext.User;
if (user.Identity.IsAuthenticated && !UserMadeAChoice(user.Identity.Name))
{
// if the current user is authenticated and he didn't made a choice
// redirect him to some page without even attempting to execute
// the controller action that he requested
var values = new RouteValueDictionary(new
{
controller = "home",
action = "index"
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
private bool UserMadeAChoice(string username)
{
throw new NotImplementedException();
}
}
Now you have different possibilities:
You decorate the controllers/actions that you want to perform this check with the [EnsureChoiceHasBeenMade] attribute
You register the action filter as a global action filter so that it applies to absolutely all actions
You write a custom filter provider in order to dynamically apply the action filter to some actions based on some dynamic values (you have access to the HttpContext).