Custom Module using Ajax.Link get Antiforgery Error

Topics: Troubleshooting, Writing modules
May 7, 2012 at 4:07 AM

I am using a custom module which has a page that displays a data grid. I have used Ajax.ActionLinks in the headers to sort the columns in my controller's action method named in the Ajax.ActionLink.

I kept getting the following exception. If I remove the [Authorize] attribute on the action method and do not sign in, then it works fine. Will I have to replace the Ajax.ActionLink with an OnSuccess and external javascript to update the html? I like using the Ajax.ActionLink because it is simpler, but maybe it does not work when authorization is used.

A required anti-forgery token was not supplied or was invalid.

My View Code looks like:

@using (Html.BeginFormAntiForgeryPost())
{
 <div class="bt-report-div">
        <table >
            <tr>
                <th class="bt-th1">
                </th>
                <th class="bt-th2">
                    <b>
                    @Ajax.ActionLink("First Name", "Sort", "Home", new { sortExpression = "FirstName" },
                    new AjaxOptions{ 
                    UpdateTargetId="registrations", 
                    HttpMethod = "POST",
                    InsertionMode= InsertionMode.Replace ,
                    Confirm = "What is happening now?",    
          
                    },
                    null
                    ) 
                    
                  </b>
                </th>
                <th class="bt-th3">
                     <b>
                    
                    @Ajax.ActionLink("Last Name", "Sort", "Home", new { sortExpression = "LastName" },
   
                    new AjaxOptions{ 
                    UpdateTargetId="registrations", 
                    HttpMethod = "POST",
                    //Url="AddToCart/SelectItem",
                    InsertionMode= InsertionMode.Replace       
           
                    },
                    null
                    )                     
                    </b>
                </th>
                <th class="bt-th1">
                <b>
                    
                    @Ajax.ActionLink("Age", "Sort", "Home", new { sortExpression = "Age" },
   
                    new AjaxOptions{ 
                    UpdateTargetId="registrations", 
                    HttpMethod = "POST",
                    //Url="AddToCart/SelectItem",
                    InsertionMode= InsertionMode.Replace       
           
                    },
                    null
                    )                     
                    </b>
                </th>
                <th class="bt-th1"><b>State</b></th>
            </tr>
            <tr></tr>
            @{
  
        var odd = false;
        foreach (var item in Model)
        {

            if (odd)
            {
                        <tr>
                            <td class="bt-td-odd1">
                                @Html.ActionLink("Edit", "Edit", new { id = item.id })
                                @Html.ActionLink("Details   ", "Details", new { id = item.id })
                            </td>
                            <td class="bt-td-odd2">@item.FirstName
                            </td>
                            <td class="bt-td-odd3">@item.LastName
                            </td>
                            <td class="bt-td-odd1">@item.Age
                            </td>
                            <td class="bt-td-odd1">@item.State</td>
                        </tr>
                odd = false;
            }
            else
            {
                        <tr>
                            <td class="bt-td-even1">
                                @Html.ActionLink("Edit", "Edit", new { id = item.id })
                                @Html.ActionLink("Details", "Details", new { id = item.id })
                            </td>
                            <td class="bt-td-even2">@item.FirstName
                            </td>
                            <td class="bt-td-even3">@item.LastName
                            </td>
                            <td class="bt-td-even1">@item.Age
                            </td>
                            <td class="bt-td-even1">@item.State</td>
                        </tr>   
                odd = true;

            }
        }
            }
        </table>
    </div>   
    }

 

My Sort Handler for the columns look like:

        [HttpPost]
        [Authorize(Users = "admin,bobt,ashley")]
        public ActionResult Sort(string sortExpression)
        {
            ViewBag.OkToDisplay = true;
            TempData["sortExpression"] = sortExpression;
            if (Request.IsAjaxRequest())
            {
                var runners = TempData["runners"] as List<RaceEntryModel>;
                switch (sortExpression)
                {
                    case "Age":
                        runners.Sort((r1, r2) => r1.Age.CompareTo(r2.Age));
                        break;

                    case "LastName":
                        runners.Sort((r1, r2) => r1.LastName.CompareTo(r2.LastName));
                        break;

                    case "FirstName":
                        runners.Sort((r1, r2) => r1.FirstName.CompareTo(r2.FirstName));
                        break;
                }
                TempData["runners"] = runners;
                return PartialView("_RaceEntries", runners);
            }
            return RedirectToAction("Index");
        }

Thanks for any suggestions.

Bob

Coordinator
May 7, 2012 at 5:28 PM

Does it help ? http://stackoverflow.com/questions/9029402/orchard-cms-ajax-anti-forgery-token-when-logged-in/9039504#9039504

Jun 23, 2012 at 6:33 PM
Edited Jun 23, 2012 at 9:24 PM
sebastienros wrote:

Does it help ? http://stackoverflow.com/questions/9029402/orchard-cms-ajax-anti-forgery-token-when-logged-in/9039504#9039504

Sorry it took so long to get back to this, but I had a very hard time figuring out how to make things really work. I did lots of searching and testing on simple test projects to determine that it apparently is not possible to use Ajax.ActionLinks to pass the built in Microsoft anti forgery token that comes with the [ValidateAntiForgeryToken] attribute for an action handler and the @Html.AntiForgeryToken() helper method inside a view. So I changed over to using a java script onclick handler inside the th tags. Then I could use the method shown in the above article and it seems to work.

I changed the Ajax.ActionLink statements in each of the th tags to just use a click handler like this:

<th class="bt-th2" onclick="sortHandler('LastName', 'Home/Sort', 'registrations');"> <b>LastName</b> </th>  
I had to put the following code into my partial view:
           @using (Script.Head())
           {
               <script type="text/javascript">

                   //<![DATA[
                    var antiForgeryToken = '@Html.AntiForgeryTokenValueOrchard()';
                   //]]>
               </script>
           }

 

And then my java script code in the external file looks like this:

function updateGridOnRegistrationPage(sortelement, url, divid, mytoken) {

    // the microsoft way...
    var token = $(':input[name=__RequestVerificationToken]').val();

    $.ajax({
        url: url,
        type: 'POST',
        // this was the microsoft way that works on non-orchard MVC 3 projects
        //data: { 'sortExpression': sortelement, '__RequestVerificationToken': token },

        // this is the Orchard way and assumes antiForgerToken has been set up in the view with its own 
        // java script section
        data: { 
            'sortExpression': sortelement,
            '__RequestVerificationToken': antiForgeryToken
            },


        success: function (data) {
            var z = data;
            $('#' + divid).html(data);
        },

        error: function (ts) {
            alert(ts.responseText);
        }
    });
}

My next question is when I have to do this on a different page, how will I be able to do this and use the same jave script function? Will I have to create another antiForgeryToken inside the other view? I also figured I had to remove the support for the Microsoft Antiforgery attributes and I could not use Html.BeginFormAntiForgeryPost. Has any of that changed in Orchard 1.4.2? I'm still using Orchard 1.4.

thanks,

Bob

Coordinator
Jun 26, 2012 at 6:17 AM

I don't understand the question. What do you mean "on a different page"?

Jun 27, 2012 at 3:12 AM

Sorry for the confusion. What I mean is that say I have two pages - say /Home/Index and /Away/Index and on each page I had a partial view where I needed to submit some data. So in both of those partial views I would include the script which sets the antiforgery token to the orchard value (as above.) I don't have a lot of experience with java script so am thinking that I cannot define the same variable in two different places. How does the UpdateGridOnRegistrationPage script figure out which one of the partial view's version of the "var antiforgerytoken" to use? Won't that token be different every time or does it magically work? I hope this better explains my concern.

Thanks,

Bob

 

Coordinator
Jun 27, 2012 at 7:09 AM

It shouldn't matter, but just to be clean, you should ensure that you're not duplicating things. The script registration API is there for that sort of thing.