CodeIgniter Forums

Full Version: Cleanest way to have some AJAX "add favorite" button?
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2

El Forum

[eluser]inktri[/eluser]
So every user profile has an "Add Favorite" button, which upon clicking should:
1. add entry to some database table if you're logged in
2. redirect you to login if you aren't logged in


Right now I'm doing something that seems quite messy:
I've got some 'ajax' controller with function add_favorite(user_id). Clicking the submit button makes a request to '/ajax/add_favorite/user_id'. Within the add_favorite function I add the record to the database table if you're logged in, load an 'error_code' view which just displays some error code number depending on if you're logged in. Within javascript with the error_code response, I either redirect page or display successful.

Is there a cleaner way to accomplish the above? Right now users could hypothetically also add favorites by just going to: BASEURL/ajax/add_favorite/user_id . I'd prefer not to allow users to do that.

Thanks for the help!

El Forum

[eluser]inktri[/eluser]
ah nevermind i'll just do the check client side initially to avoid having to use error codes

edit: actually that won't work so the question still stands!

El Forum

[eluser]Jamie Rumbelow[/eluser]
You can use Michael Wales's Request Library to check if the Request has been made via ajax. The just call the controller for both events - ajax and non ajax. if it's an ajax request, output what you need to, and vica versa.

El Forum

[eluser]Nick Husher[/eluser]
I think you're asking two different questions: the first is a design question--what's the best practices approach to handle ajax requests? The other is, in essence, a security question--how do I prevent request forgery issues.

To answer the second question first, the way I've handled it in the past is to require a public key associated with single-request server-altering actions. For instance, if the user can click a link to delete an element from the database, that link will have a hash of their password (I seem to remember running their password through MD5 twice, then taking the last ten characters). Theoretically, the user could browse to a url like http://www.example.com/ajax/delete_asset/5/fcf091f0b1, but it's far less likely. In addition, it makes it very difficult for a malicious user who doesn't know how the system works internally and who doesn't have a user password to perform unchecked mischeif through your data interface.

To answer the first question -- I really don't know. Is there a best practices that anyone else can expand upon?

El Forum

[eluser]Jay Turley[/eluser]
I personally drop a token into my ajax POST requests in order to make sure the request is valid. No token = no action.

El Forum

[eluser]yelirekim[/eluser]
If you're using standard calls to perform AJAX actions (XMLHTTPrequest and derivatives, used by every major js library), you can just use session validation for the specific instance that you're talking about here. Since you only want to update the database if they are logged in, just go right ahead and read session data server side on the AJAX call to determine that. With that hurdle out of the way, the whole process you're talking about isn't very difficult, I'll write out basically what you would do here with Prototype:

edit: i guess the XSS filter took out my script tags, but i'm sure you can figure that out

Code:
<a id="fav_button" href="add_favorite/&lt;?=$cat_name?&gt;/&lt;?=$etc?&gt;" class="fav_button">add to favorites</a>
[removed]
    $('fav_button').observe('click',fav_request);
    function fav_request(evt)
    {
        Event.stop(evt); //stop the link from working
        new Ajax.Request($('fav_button').readAttribute('href'));
    }
[removed]

Code:
function add_favorite($category,$etc)
{
    if($this->user->logged_in())
    {
        //do some db stuff
    }
    else
    {
        header("Content-Type: text/javascript");
        echo "code for javascript redirect";
    }
}

There are two things going on here that might get slightly confusing

1. If the response to an AJAX call made by prototype is headed as javascript, prototype will automatically run that javascript
2. We're intercepting what would normally be a hyperlink to the add comment page and replacing it with an AJAX call

as far as best practices go here, the part you're saying about people just going to the URL in order to add a favorite should be looked at as an advantage instead of something you're trying to avoid. Just add a little post data into this setup here to determine whether someone is 'viewing' that page as an AJAX request, or whether they browsed to it, add in a little message that says "thanks for adding a favorite", and the whole thing will work with or without javascript. So if they have JS disabled, or your code is broken, or they are using an old browser or they are on their cell phone... etc etc, it's always good to build in alternatives to javascript, it's even better if existing javascript gracefully degrades to the non-js version.

El Forum

[eluser]Nick Husher[/eluser]
The problem with that is that it opens you up to request forgery attacks or simple accidental request problems. Let's say you have a CI app at example.com that has a controller called 'record_handler' that contains a method called 'delete_record' that takes an arbitrary record ID. Users can either click on a regular HTML link to that URI (example.com/ajax/delete_record/{ID}), or a link-intercepting AJAX script can perform the request asynchronously, which will delete a particular record from your database--it even checks the session first to make sure you're logged in. This seems safe, but what happens if someone posts an image on a forum <img src="example.com/ajax/delete_record/10" /> and someone with an open session happens to land on that page? The browser sees the image URI and, not knowing if the resource is actually an image or not, hits the server. The server checks the session and finds it valid, so it deletes the record.

There are a few ways to avoid cross-site request forgery attacks:
First, check the referrer header to see who pointed the client at the URL. Unfortunately, not all systems will correctly send or recieve this header property.

Second, you can add a second-step confirm that requires a response with a unique transaction ID associated with it. So, the user sends a delete request, the server sends back a transaction ID, the client then responds with a delete command with the transaction ID attached. This becomes architecturally complex, especially if you're building your site to work without AJAX and are using javascript for progressive enhancement.

Third, you can send a semi-private key that acts as an authorizer. Basically, you want to use a data field that can't be guessed by potential attackers or accidentally fallen upon by unwary users. Adding a password hash to the URL isn't a perfect solution, but the longer the hash key is, the less likely a random person will be able to formulate an HTTP request that will slip through.

Also, it's important to note that this is only a problem when irreversable changes to your system are being carried out: generally information retrieval of nonsensitive data can be performed without paying attention to whether or not it's a request forgery or not.

El Forum

[eluser]yelirekim[/eluser]
Right... but I thought we were talking about an add to favorites button? I understand that there are security implications there, but the thing you have to consider in any situation is: does this really matter? Ok sure, if the link would delete their account, then you wouldn't want to use this method. But for an add to favorites button, what do you think the likelihood, or detrimental effect would possibly be of people maliciously trying to hack this specific function of your site?

El Forum

[eluser]Pascal Kriete[/eluser]
I could add my own site to all my visitor's favorites (assuming they are logged in). Talk about a great SEO strategy Wink .
[EDIT: Looks like favorite users, so replace 'site' with 'account' above. Samy anyone?]

El Forum

[eluser]yelirekim[/eluser]
DUDE WE HAVE THE SAME SIGNATURE

but actually you're right about someone doing that for SEO purposes, I see the error of my ways
Pages: 1 2