i'll try putting it in brief, but not miss out on the details O-)
My customer wanted to organize a photo contest. Users upload their photo, browse photos, vote for those they like. Most votes = 1st place. Less votes - lower place. Voting is anonymous.
As always, it all looked simple in the very beginning.
Before setting off with coding, you'll probably think for a while; that's what i also tried doing and, well, "try" in general.
The main question was - 'what should be considered a vote?' (which changed into 'what should be considered a "valid" vote' later on).
From my point of view (and state of 'knowledge' at the time), there were 2 ways to address the issue.
1) Ip-fixed voting
'One ip may vote once per day' (which consequently means 'for 1 photo')
You would implement this approach in the following way.
You have a 'votes' table. Before inserting an entry into it, you check that there is no entry "date-ip" inside it. If there is an entry - you turn down the attempt.
The drawback of this approach was, that all 'employees', sitting behind a corporate router with NAT lose the individual ability to vote. The entire office can vote for 1 photo once per day.
I don't know how it is in other countries, but in Russia, you wouldn't really want to lose such a big audience. Thus, we decided to avoid this approach.
2) Cookie/Session-fixed voting.
It might be wrong to combine the 2 things into 1, but in many books i've read, they are distinguished, but in nature - they are the same. When you start a session for a browser, you actually set a cookie on the user's machine (probably with a SID or PHPSESSID or something like it).
This approach is a bit more complex. You set a session cookie for the user. Once a vote has been made, you do something like:
$_SESSION['voted'] = true;
you use this to prevent the user from voting again within the current session)
setcookie('voted', true, mktime(24, 0, 0) [...] );
you use this to keep a 'returning user' from voting again.
The drawback here is, all you need to do to vote again is reset the cookies.
So, in the end, the 1st approach meant you really cut up your audience, the 2nd - you are expecting really naive users.
But, here's the best part.
The first wave of attack were 'brutalists' - people turning off cookies and hitting the voting page fast (it lays in the open and had a very misleading name 'vote.php?id=xx', where 'xx' is the photo number O-).
Brutalists were kicked a bit off by placing 2 checks. The idea was that, first, a voting page should not be hit directly. You put a check, that some sort of session variable had been defined previously; the voting page is supposed to 'intercept' a previously defined session and not 'start a new one', although it's sessions_start() all the same. The second check was on number of 'pagehits'. Voting is only possible after at least 1 page (photo-browsing page) had been hit. Now, in order to vote, you first had to land on some page (not necessarily the photo browsing page), get a session, and then you could vote.
Then came the "slavers". They did something wicked, but something every web developer should experience once (i guess) and expect further on.
Forums, blogs - most of them don't have restrictions on using the <img src="external_url"> and <a href=""> tags. Well, guess what?
They place 2 images referencing your site with the photo contest in their profile, place for avatar or in their post. Then they start posting.
The first images gets a session and cookie set to every browser looking at any page, where their profile/post appears 'somehow' (and does it in a quiet way - i use firefox to debug 'things', it didn't even ask me about setting a 'foreign' cookie, but it actually did). The second, third, fourth, etc. image submits the vote O-).
The math is somethig like this:
Entries with references * Number of references inside the entry * Page hits (slaves) = votes
You look into the DB and see, that within the last few hours 2 or 3 photos have gained quite a number of votes from lots of different ips.
The conclusion you make after this, is 'having something like vote.php' out there in the open and so clearly defined is a bad idea (use front controller frameworks, read more, think more - then you might get good ideas).
So, that's how i got stuck with a number of questions.
- Hide vote.php? Have it move around all over the project? (thus, breaking all links like vote.php?id=xx)
- Obfuscate it? (add some sort of additional params so as to have links, that are unique in each voting case? it seems this would also break such linking attempts)
- Try moving it all into flash? The photo browsing is done via a flash app that makes requests to an AMFPHP gateway. Perhaps, voting could also be welded into it.
- Or was the idea of not restricting voting to ips very wrong from the very beginning and there is no way to create valid anonymous voting?
- Is web developing really something that i should be involved with? O-)
All opinions on any issues are appreciated.