I Don’t GET It

Safer Deletes Through POST Links in ASP.NET MVC

Besides providing an elegant .NET-based web application platform, ASP.NET MVC (as packaged in Visual Studio 2010) can help you crank out a decent CRUD interface before your coffee goes cold. Well, almost.

For reasons that aren’t quite crystal to your humble narrator, they leave the “D” as a lonely exercise for the MVC novice, and munsoning this implementation could result in a loophole in your architecture that could hang your data out to dry.

What I’m circumlocuting here is the peril of implementing your Delete as an HTTP GET action, rather than as an HTTP POST. On the surface, it makes sense; a Delete call looks much more like a Read call than a Create or Update does, since the only argument is the item’s key. The danger in this implementation, however, is clarified by Stephen Walther in number 46 of his ASP.NET MVC Tips series:

In theory, someone could send an email to you that contains an image. The image could be embedded in the message with the following tag:

<img src=”http://www.theApp.com/Home/Delete/23” _fcksavedurl="”http://www.theApp.com/Home/Delete/23”" />

Notice that the src attribute points at the Delete() method of the Home controller class. Opening the email (and allowing images in your email client) will delete record 23 without warning. This is bad. This is a security hole.

Walther goes on to demonstrate solving this problem from several different angles, to which I have boiled down for your consumption one straightforward jQuery-based approach. First, go server-side and enforce that your Controller‘s Delete method only responds to POST requests using an AcceptVerbsAttribute:

//
// POST: /Thing/Delete/5
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(int id)
{
    // TODO: Delete the thing!

    // go back to the main view
    return RedirectToAction("Index");
}

Next, include this jQuery function in your Master Page:

$(document).ready(function () {

    // force .post links to POST
    $("a.post").live('click', function (e) {

        // all of your default behavior are belong to us
        e.preventDefault();

        // POST and reload on callback
        $.post(this.href, null, location.reload);
        
    });
    
});

This function will attach itself to any anchor tag bearing the “post” class and intercept its natural event behavior, which is to GET the given URL. It will then asynchronously POST to said URL, and refresh the page when that operation completes. For extra points, you can do something far more intriguing than calling location.reload here. I mean, it’s 2k9. I think the time has come for a custom animation in which our deleted record grows legs and walk off the screen, exuding a sort of romantic sadness. Don’t you?

Meanwhile, back in your view, apply the “post” class to any link which engages in dangerous and questionable data-bending behavior:

<a href="/thing/delete/<%= thing.ID.ToString() %>" class="post">Delete</a>

Now, should some click-aggressive philistine navigate to this URL from outside the cozy boundaries of your application, they will receive an HTTP 404. This will likely be followed up with a terse call to the helpdesk announcing that OMG THE WEB SITE IS DOWN. But unlike the hapless web dude fielding that call, your data will be safe and sound.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s