views:

1351

answers:

4

As part of the ASP.NET MVC 2 Beta 2 update, JSON GET requests are disallowed by default. It appears that you need to set the JsonRequestBehavior field to JsonRequestBehavior.AllowGet before returning a JsonResult from your controller.

   public JsonResult IsEmailValid(...)
   {
        JsonResult result = new JsonResult();

        result.Data = ..... ;
        result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;

        return result;
    }

What is the reasoning behind this? If I am using JSON GET to try and do some remote validation, should I be using a different technique instead?

+8  A: 

The reason for the DenyGet default is on MSDN with a link to Phil Haack's blog for further details. Looks like a Cross-Site scripting vulnerability.

Chris Shaffer
so if I JsonRequestBehavior.AllowGet ... will this vulnerability continue to exist or it produces some protection or what ?If I need to return Json data then I would return Json data without Explicity repeat myself ???!!!!
jalchr
+1  A: 

I don't know if this is the reason they chose to change that default, but here's my experience:

When some browsers see a GET, they think they can cache the result. Since AJAX is usually used for small requests to get the most up-to-date information from the server, caching these results usually ends up causing unexpected behavior. If you know that a given input will return the same result every time (e.g. "password" cannot be used as a password, no matter when you ask me), then a GET is just fine, and browser caching can actually improve performance in case someone tries validating the same input multiple times. If, on the other hand, you expect a different answer depending on the current state of the server-side data ("myfavoriteusername" may have been available 2 minutes ago, but it's been taken since then), you should use POST to avoid having the browser thinking that the first response is still the correct one.

StriplingWarrior
Very interesting idea... do you have any links to which browsers might try to cache a GET result and under what circumstances?
Jedidja
The most prevalent example is Internet Explorer (http://greenash.net.au/posts/thoughts/an-ie-ajax-gotcha-page-caching), but it's not so much a question of which browsers do it as the fact that the HTTP specification says specifically that data retrieved with a GET request is cacheable. The ASP.NET's AJAX framework may set headers to tell the browser that the content shouldn't be cached, though, so that may not have anything to do with this particular issue. I ran into this problem when I was writing my own javascript and servlet pair to communicate via AJAX.
StriplingWarrior
I have run into the caching issue several times now and using POST (even disregarding the security issues) is definitely the way to go.
Jedidja
+4  A: 

HTTP GET is disabled by default as part of ASP.NET's Cross-Site Request Forgery (CSRF/XSRF) protections. If your web services accept GET requests, then they can be vulnerable to 3rd party sites making requests via <script /> tags and potentially harvesting the response by modifying JavaScript setters.

It is worth noting however that disabling GET requests is not enough to prevent CSRF attacks, nor is it the only way to protect your service against the type of attack outlined above. See Robust Defenses for Cross-Site Request Forgery for a good analysis of the different attack vectors and how to protect against them.

Annabelle
+1  A: 

I also had your problem when I migrated my MVC website from Visual Studio 2008 to Visual Studio 2010.

The main aspx is below, it has an ViewData which calls a Category Controller in order to fill up ViewData["Categories"] with SelectList collection. There's also a script to call a Subcategory Controller to fill up the second combo with javascript. Now I was able to fix it adding up AlloGet attribute on this second controller.

Here's the aspx and javascript

<head>
<script type="text/javascript" src="../../Scripts/jquery-1.4.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#CategoryId").change(function () {

    var categoryId = $(this)[0].value;

    $("#ctl00_MainContent_SubcategoryId").empty();
    $("#ctl00_MainContent_SubcategoryId").append("<option value=''>-- select a category --</option>");
    var url = "/Subcategory/Subcategories/" + categoryId;

    $.getJSON(url, { "selectedItem": "" }, function (data) {
        $.each(data, function (index, optionData) {
            $("#ctl00_MainContent_SubcategoryId").append("<option value='" + optionData.SubcategoryId + "'>" + optionData.SubcategoryName + "</option>");
        });
        //feed our hidden html field
        var selected = $("#chosenSubcategory") ? $("#chosenSubcategory").val() : '';
        $("#ctl00_MainContent_SubcategoryId").val(selected);

    });

}).change();
});
</script>
<body>
<% using (Html.BeginForm()) {%>
<label for="CategoryId">Category:</label></td>
<%= Html.DropDownList("CategoryId", (SelectList)ViewData["Categories"], "--categories--") %>
<%= Html.ValidationMessage("category","*") %>
<br/>
<label class="formlabel" for="SubcategoryId">Subcategory:</label><div id="subcategoryDiv"></div>
<%=Html.Hidden("chosenSubcategory", TempData["subcategory"])%>
<select id="SubcategoryId" runat="server">
</select><%= Html.ValidationMessage("subcategory", "*")%>
<input type="submit" value="Save" />
<%}%>                

here's my controller for subcategories

public class SubcategoryController : Controller
{
    private MyEntities db = new MyEntities();

    public int SubcategoryId { get; set; }
    public int SubcategoryName { get; set; }
    public JsonResult Subcategories(int? categoryId)
    {
        try
        {
            if (!categoryId.HasValue)
                categoryId = Convert.ToInt32(RouteData.Values["id"]);
            var subcategories = (from c in db.Subcategories.Include("Categories")
                                 where c.Categories.CategoryId == categoryId && c.Active && !c.Deleted
                                  && c.Categories.Active && !c.Categories.Deleted
                                 orderby c.SubcategoryName
                                 select new { SubcategoryId = c.SubcategoryId, SubcategoryName = c.SubcategoryName }
            );
            //just added the allow get attribute
            return this.Json(subcategories, JsonRequestBehavior.AllowGet);
        }
        catch { return this.Json(null); }

    }
Junior Mayhé