15

I have two actions inside my controller (shoppingCartController)

    public ActionResult Index()
    {
        //some stuff here
        return View(viewModel);
    }


    public ActionResult AddToCart(int id)
    {

        return RedirectToAction("Index");

    }

Is there anyway to prevent the users from directly calling the index action by typing the url in the browser?

For example: If the user browses to shoppingCart/index be redirected to Home/Index.

Jed
  • 10,649
  • 19
  • 81
  • 125
Amir Jalali
  • 3,132
  • 4
  • 33
  • 46

8 Answers8

18

You could use the [ChildActionOnly] attribute on your action method to make sure it's not called directly, or use the ControllerContext.IsChildAction property inside your action to determine if you want to redirect.

For example:

public ActionResult Index()
{
    if(!ControllerContext.IsChildAction)
    {
       //perform redirect here
    }

    //some stuff here
    return View(viewModel);
}

If you can't make the Index action a child action, you could always check the referrer, understanding that it's not foolproof and can be spoofed. See:

How do I get the referrer URL in an ASP.NET MVC action?

Community
  • 1
  • 1
Eric King
  • 11,594
  • 5
  • 43
  • 53
  • if i put [ChildActionOnly] on my index method it wont be called from redirectToAction. it only works if i have html.action(). also controllerContext.ischildAction doesn't recognize redirecttoaction as childaction call. – Amir Jalali Feb 23 '12 at 04:59
  • If you want an action that works like a regular action (i.e. you can RedirectToAction to it) but can only be redirected to from one place, you could try to check the referer inside the action, redirecting to home/index if the referer isn't the AddToCart url. That wouldn't be foolproof, though, as referer can be spoofed. – Eric King Feb 23 '12 at 05:11
  • i was going to check the response StatusCode. how should i check the referere? – Amir Jalali Feb 23 '12 at 05:18
  • http://stackoverflow.com/questions/1471188/how-do-i-get-the-referrer-url-in-an-asp-net-mvc-action – Eric King Feb 23 '12 at 05:21
  • i checked the Request.UrlReferrer it did't show which action it comes from it showed which url it comes from. – Amir Jalali Feb 23 '12 at 05:44
  • @cSharpper Yes, that's the point. If the referrer shows the request came from some other url (or none), then redirect to home. – Eric King Feb 23 '12 at 13:14
4

Try making this Index controller action as private. A method with private access modifier should not be accessible from outside class.

And then, rahter than calling RedirectToAction from AddToCart call it as simple method like below:

private ActionResult Index()
{
    //some stuff here
    return View(viewModel);
}


public ActionResult AddToCart(int id)
{

    return Index();

}
Maheep
  • 5,539
  • 3
  • 28
  • 47
  • but it doesn't redirect the users to to home/index. – Amir Jalali Feb 23 '12 at 04:33
  • so it doesn't solve the problem of redirecting the user if they explicitly entered the url shoppingCart/Index it just gave the some error that the resources can not be found – Amir Jalali Feb 23 '12 at 04:49
  • Ok. What you have in Index should be some helper method which should be called from AddToCart. And Actual Index method should be public and shuld redirect to Home/Index. – Maheep Feb 23 '12 at 05:03
  • I'd suggest using `internal` instead of `private`. That way it can still be made accessible to test projects. – Micteu Dec 14 '16 at 22:14
3

If SessionState is enabled, you can use controller's TempData to achieve your goal. Set TempData in AddToCart action, and only display Index view if Index action can get TempData key set in AddToCart action.

If you need this for multiple actions/projects, use combination of custom Action Filter and ActionResult. Like this:

// Controller
[PreventDirectAccess]
public ActionResult Index()
{
    //some stuff here
    return View(viewModel);
}

public ActionResult AddToCart(int id)
{
    return new PreventDirectAccessRedirectToRouteResult(new RouteValueDictionary
    {
        {"action", "Index"}
    });
}

// Filter
public class PreventDirectAccessAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext == null)
            throw new ArgumentNullException("filterContext");

        if (filterContext.Controller.TempData[PreventDirectAccessRedirectToRouteResult.Executed] == null)
            filterContext.Result = new HttpNotFoundResult();

        base.OnActionExecuting(filterContext);
    }
}

// ActionResult
public class PreventDirectAccessRedirectToRouteResult : RedirectToRouteResult
{
    public const string Executed = "PreventDirectAccessRedirectExecuted";

    public override void ExecuteResult(ControllerContext context)
    {
        context.Controller.TempData[Executed] = true;
        base.ExecuteResult(context);
    }
}
bmks
  • 39
  • 1
2

NonAction is the attribute to use. It is part of the MVC library, so you don't have to create your own attribute.

Represents an attribute that is used to indicate that a controller method is not an action method.

Adrian Thompson Phillips
  • 6,893
  • 6
  • 38
  • 69
Linda
  • 21
  • 1
  • I understood NonAction from this post: https://www.tutorialspoint.com/what-is-the-significance-of-nonactionattribute-in-asp-net-mvc-chash (that msdn link is unfortunately useless); thanks for sharing such an easter egg! – gawkface Jun 09 '21 at 04:16
2

If all you are worried about is the user typing in the URL, then using the HttpPost attribute should prevent your action from being called that way:-

[HttpPost]
public ActionResult AddToCart(int id)
{

This prevents GET requests from calling that action. It doesn't however stop someone writing a dummy form and POSTing to your action.

If you are worried about something a little more malicious, you might want to implement some form of anti-forgery token, there's some good info on that here.

EDIT

OK, so on re-reading the question the above doesn't quite address your issue.

How about a route? if you had something like the below, it would prevent ShoppingCart/Index being called and redirect the user to your site index.

        routes.MapRoute(
            "ShoppingCartIndex",
            "ShoppingCart/Index",
            new { controller = "Home", action = "Index" }
        );
Aleks
  • 1,629
  • 14
  • 19
  • I don't think it's the AddToCart action that he wants to protect, it's the Index action, which is a GET. – Eric King Feb 23 '12 at 05:13
  • 1
    But you're right, the AddToCart action definitely needs to be a POST. :-) – Eric King Feb 23 '12 at 05:15
  • no im worried about user typing example.com/shoppingcart and see the empty shoppingCart. – Amir Jalali Feb 23 '12 at 05:16
  • If all you're worried about is an empty shopping cart page, why don't you just check the shopping cart contents in the Index action and redirect to Home if it's empty? – Eric King Feb 23 '12 at 05:30
  • If it's about the cart being empty, could you check the number of items in the Index action of the ShoppingCart controller and redirect back if the quantity is zero? – Aleks Feb 23 '12 at 05:31
  • yeah i could check the number but it was just a simple example its more complex action. – Amir Jalali Feb 23 '12 at 05:35
  • making a route makes every call to this action redirecting to home – Amir Jalali Feb 23 '12 at 05:38
1

Here is written code how to prevent browser direct access to action method: Write below code in FilterConfig.cs

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class NoDirectAccessAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.UrlReferrer == null ||
                    filterContext.HttpContext.Request.Url.Host != filterContext.HttpContext.Request.UrlReferrer.Host)
            {
            filterContext.Result = new RedirectToRouteResult(new
                           RouteValueDictionary(new { controller = "Home", action = "Index", area = "" })); 
        }
    }
}

Now apply this code on your action method

[NoDirectAccess]
public ActionResult MyActionMethod()

This will restrict to call directly any class or action method.

Anjan Kant
  • 4,090
  • 41
  • 39
0

You also can use child action only attribute. Just sharing :)

public class MyController {

  [ChildActionOnly]
  public ActionResult Menu() {
    var menu = GetMenuFromDB();
      return PartialView(menu);
  }

}
B M
  • 1,863
  • 1
  • 25
  • 40
0

This is untested, but I believe you can use the Validate Anti Forgery Token

In your code on the page, you will need to place an validation token in the form that is to be posted:

@Html.AntiForgeryToken()

Which produces:

<input name="__RequestVerificationToken" type="hidden" value="s9+jDREFMlNPkAT2zOlmhJZQbbDOzMhuarSTG1BVAC4GeHiNL5VtuQo7CQTF8obw8hEYIQac9YaQh+qVcF0xj0eNO7lVdezz+JxuSKGQo2d2gEdtkEdR+XTTFas4Gh6fjSYc7A1rWF8AAhxjZ9j6GlbRhECZOPAlPAItnjz49QQ=" />

This token is automatically picked up by any action that has this attribute:

[ValidateAuthenticationToken]
public ActionResult AddToCart(int id)
{
   return Index();
}

If the request is direct then an error will occur.

Dan
  • 12,808
  • 7
  • 45
  • 54
  • I like this idea but what if this link I have is not in a form? – retslig Oct 03 '12 at 19:50
  • 1
    @retslig - If you mean as a standard GET request, [then you can't use this method](http://stackoverflow.com/a/4943610/59532) – Dan Oct 03 '12 at 22:04