I'm sure you're all familiar with the voting systems that use AJAX (Um... look right over there <----)
I have something similar and when you vote up or down it uses AJAX to request the new value from votes.php. The problem is that I am using a session to get the userid so a person can only vote once. What happens if they sit on the page for an hour and then vote so the session is no longer there? What would be a good way of handling this situation? Should I redirect their page to the login screen? If so, how can I do that from the votes.php page that is being referenced by the AJAX request? Am I overlooking a good way of handling this situation? Any advice would be helpful.
Consider returning an http status of 401, and a JSON object detailing the reason. If you're using jQuery, that'll drop you to the error() callback, which you can then parse your object.
$.ajax({
data: {},
dataType: 'html',
success: function(data) {
// do whatever here
},
type: 'POST',
url: 'myserver.com',
error: function(XMLHttpRequest, textStatus, errorThrown) {
// XMLHttpRequest.responseText has your json string
// XMLHttpRequest.status has the 401 status code
if (XMLHttpRequest.status === 401) {
location.href = 'login.php';
}
}
});
I'm not familiar with PHP anymore, but this should work for just about any environment. You may have to suppress any automatic login form redirection though. In asp.net mvc the framework will see the 401 and push the default login form back, with a status of 200.
You should only store a link to the users identity in the session. Use sessions to identify a user as x and then get user x's information from the database.
If your problem is with users sessions timing out then you should reconsider how you're using your sessions. Perhaps make them last until the browser closes? If you really want to make them a duration, then perhaps ping the server in intervals to keep the session alive.
Decide in your php script whether or not the user should be able to vote. If the session isn't set, or if they have already voted, return a message that you can identify with on the client side. If they already voted perhaps return "voted":"true" in a JSON object. Use JS to parse this object and understand what it means, taking the appropriate action. If the session isn't set, perhaps return "session_set":"false", and then make javascript redirect with a window.location = "login.php" etc.
Only increment the counter for the user on a successful return of a counted vote.
This is an old thread, but I wanted to share my solution that is working really well.
In my framework the system redirects the user to the login form any time they try to access a page and the session has timed out or is not valid.
I added to the top of the login form the following html comment:
<!--LOGINFORM-->
I created a wrapper for jQuery's $.ajax function which checks for this string on every request, and if it is there it shows a dialog popup saying that their session has timed out.
You can use this by just calling:
ajax.get('http://someurl.com', function(data){
//Do stuff
});
Hope it helps someone.
var ajax = {
check_login : function(resp){
if (resp.substring(0, 16) === "<!--LOGINFORM-->"){
// Show a popup or redirect them to login page!
return true;
}
return false;
},
get : function(url, success){
if (typeof data =='undefined'){
data = null;
}
$.ajax({
url: url,
type : 'GET',
success : function(resp){
if (!ajax.check_login(resp)) {
success(resp);
}
},
});
}
};
You structure the Javascript code that makes the Ajax request to accept a special result (say, -1 where a >=0 number would normally be, such as, a count of votes) to mean "sorry bub, you're timed out" and redirect to the re-login page (which can take as an optional parameter a message explaining to the user they timed out, &c).
You could create a javascript function that could ping the server every 10 minutes via something like
setTimeout("Ping()", 60000);
If you want to navigate the user to the login page if they connect with a faulty session then I would first verify the session and if it fails send a
header("Location: ...");
http://ca2.php.net/manual/en/function.header.php
From a user perspective, the best solution is to pop up a message and login form, saying something like "You are not logged in or your session timed out". Digg does this very well.
As for the actual AJAX implementation, swilliams' 401 suggestion is solid. Alternatively, you can simply return a specific string on failure.
Related
Imagine that I have a page, with a login/logout function in the header, which works fully via AJAX.
If a user isn't logged in, I want to show him "Hey, login" and allow him to login via ajax. That's all good, and it works.
But if the user logs in, I can't access anything that DIDN'T get put on the page -- the content that is there when the user accesses the page when they are successfully logged in.
If I view the page source, elements are there, but if I do things such as $("#container").show(), .slideDown(), .html(), none of them are doing anything.
Do I have to make the user refresh the page, or store all of the code in javascript to deploy? I hope not.
I've included my Javascript for the login below, but that's not really the point. That's working. Where I say $("#all").slideDown() is the problem. Even when accessing it through the console, it won't do anything.
The problem is probably related to dying with PHP if the user isn't logged in. This code is executed in $(function() { }
Code:
/* Get the AJAX Login form Ready */
$("#login-form").submit(function() {
$("#login-error").hide();
var username = $("#username").val();
var password = $("#password").val();
if (username == "" || password == "") {
$("#login-error").show();
$("#login-error-heading").html("You've entered something wrong.");
$("#login-error-content").html("Please enter both a username and a password.");
return false;
}
$.get(
"process-login.php",
{
username:username,
password : password
}, function(data) {
if (data.success == 1) {
/* Hide the login form */
$("#login").modal("hide");
/* remove the login button */
$('#nav-login').remove();
/* add the logout button to the DOM */
$("#nav-container").append('<ul id="user-options" class="nav pull-right"><li class="divider-vertical"></li><li class="dropdown">Logged in as ' + data.username + '<b class="caret"></b><ul class="dropdown-menu"><li>Logout</li></ul></li></ul>');
$("#all").slideDown();
} else {
$("#login-error").show();
$("#login-error-heading").html("You've entered something wrong.");
if (data.success == 3) {
$("#login-error-content").html("This account is locked out because of incorrect password usage. Please try again in 10 minutes.");
} else {
$("#login-error-content").html("The username and password combination was not found.");
}
}
},
"json"
);
return false;
});
First of all, I don't think it's a good idea to use AJAX to divide your presentation logic. I'd use PHP to detect whether they're logged in first, and separate the views accordingly.
If you must use AJAX to do this, I'd suggest redirecting because that's what I like when I log in. It feels like a fresh start. If you don't want to redirect, why don't you put all of the layout changes in a separate function like:
function openSesame() {
$('div.welcome_page').slideUp();
$('div.treasure_trove').show();
}
And call it in your AJAX's success setting.
Without code, we can't do much. I would check to make sure that you aren't attempting to use a bound event on something that didn't exist at the time of the initial bind. If something doesn't exist until your AJAX callback, then you'll have to bind it after it's been loaded.
I may be wrong, but I believe the issue is that your new content, while in the DOM isn't bound to any of the jquery event handlers. The way to bind events to future elements is to use the .on() method, like so:
$("#dataTable tbody tr").on("click", function(event){
alert($(this).text());
});
As #jamie said, don't mix JS and business logic if you don't have to.
This is where you need to use a templating engine, and make an attempt at building something that resembles MVC. Check out Smarty http://www.smarty.net/, or if you really want a legit MVC framework you can go with CakePHP or Symfony.
You do not need to redirect. It's slow and not exactly enjoyable for the user.
Let's just take a pseudo-code example using Smarty:
global $smarty;
//go grab your user object
$user = PseudoUser::validate_current_user();
if( $user->login_status === true )
{
//user is logged in
$smarty->assign( 'user', $user );
$smarty->display( 'logged_in_template.tpl' );
}
else
{
//user is logged out
$smarty->display( 'logged_out_template.tpl' );
}
Bada bing bada boom.
With the js ajax login, yes, you should just refresh the page in this case. Your PHP controller should have already established what to do when the page is reloaded.
It sounds like you have a javascript error on your page. I'm assuming your logged in section is being served over HTTPS. If that's the case, and if your javascript functionality is stored in a .js file, then you might need to include your javascript file over https as well.
Using a network path reference (or protocol agnostic path) helps resolve these kinds of https vs http issues.
<script src="\\js\myJsFile.js"></script>
Edit:
You might try the $.ajax function so that you can catch any errors that do occur... This might tell you more about what is actually happening with your request.
$.ajax({
url: "process-login.php",
data: {
username: username,
password: password
},
success: function (data) {
// your function
},
error: function (jqXhr, error) { // see http://stackoverflow.com/a/1956505/296889
alert("readyState: " + jqXhr.readyState + "\nstatus: " + jqXhr.status);
alert("Response Received: " + jqXhr.responseText);
},
dataType: 'json'
});
I have made a simple chat application which uses long-polling approach using jquery,
function sendchat(){
// this code sends the message
$.ajax({
url: "send.php",
async: true,
data: { /* send inputbox1.value */ },
success: function(data) { }
});
}
function listen_for_message(){
// this code listens for message
$.ajax({
url: "listen.php",
async: true,
timeout:5000,
success: function(data) { // the message recievend so display that message and make new request for listening new messages
$('#display').html(data);
listen_for_message();
}
});
}
THIS SHOULD HAPPEN : after page loaded the infinite request for listen.php occurs and when user sends message, the code sends message to database via send.php.
PROBLEM is, using firebug i've found that send.php request which is performed after listen.php request, is remains pending. means the request for send message is remains pending.
The issue was because of session locking;
both send.php and listen.php files use session variables,
so session is locked in listen.php file and the other file (here send.php file) can't be served after the session frees from serving another file ( here listen.php).
How do I implement basic "Long Polling"?
the link above is a similar question that may help you.
it does not have to be on a database, it can be saved on a tmp file, but your problem is that you are choking the browser by performing too many requests, any one browser handles two requests at a time, which means you should really allow the browser to finish the first requests first then do the second one... and so on...
you do not need to do send.php and listen.php, because you can do it simply on one page both of them.
function check(){
$.ajax({
url : 'process.php',
data : {msg:'blabla'/* add data here to post e.g inputbox1.value or serialised data */}
type : 'post',
success: function (r){
if(r.message){
$('#result').append(r.message);
check();//can use a setTimeout here if you wish
}
}
});
}
process.php
<?php
$msg = $_POST['msg'];//is blabla in this case.
$arg['message'] = $msg;//or grab from db or file
//obviously you will have to put it on a database or on a file ... your choice
//so you can differentiate who sent what to whom.
echo json_encode($arg);
?>
obviously this are only guide lines, but you will exhaust your bandwidth with this method, however it will be alot better because you have only one small file that returns either 0 to 1 byte of information, or more if there is a message posted.
I have not tested this so don't rely on it to work straight away you need a bit of changes to make it work but just helps you understand how you should do it.
however if you are looking for long pulling ajax there are loads of scripts out there already made and fine tuned and have been test, bug fixed and many minds help built it, my advice is don't re-invent the wheel
Frankly, it's just causing too much hassle in in v1.0 to have a functionality which requires three form submissions, with $_SESSION session data holding all of the intermediate stuff - only to have a user start an operation, then open a second tab and perform a second operation which tramples over the session data.
I doubt that this is malicious (but can’t discount it). More likely the user starts an operation, gets interrupted, forgets that they started or can’t find the original tab so starts again (then later finds the original tab and tries to complete the operation a second time).
Since I am coding in PHP I can detect the existence of session data on form submission (how would I do that with JS if the user as much as opens another tab – I guess that I would need Ajax – right?).
So, each time I start an operation I check for a flag in session data and if set I reload to a “I’m sorry, Dave. I’m afraid I can’t do that” page, else I set the flag and continue (remembering to clear it at the end of the operation).
I guess that that would work, but:
1) Is it acceptable to restrict browser apps to a single tab/instance?
2) Should I attempt to allow multiple instances in v2.0 ?
Any other comments, help or advice?
A better design would be to avoid storing user interaction state in the session. Put it in hidden form fields or something so that each client request carries its associated state with it. If you're concerned about the user tampering with it, use an HMAC to prevent that, and possibly encrypt it if it contains things the user shouldn't be able to see.
Only state that should be shared between tabs — like the user's login identity, or something like a shopping cart — should be stored in the session.
At most you can is keep a "last requested page" listing in the session file, with flags to indicate that the user shouldn't be allowed to move off it if it's one of these critical form flags. So if you're on form.php and it's a no-move-off one, then any new page loaded should present an "abort or close window" option.
You cannot prevent a user from opening up another tab/window, but you can prevent them from moving elsewhere in your site in those other windows/tabs.
However, consider that this is a very poor user experience. Imagine if Amazon trapped you in the shopping cart page and never let you on to another page without having to actually buy something. Consider updating your code to allow multiple different windows use the same form.
With every browser supporting tabbed browsing it would be a poor user experience to try to restrict browsing to a single tab (you might as well make a desktop app then).
One way you could solve this is by adding a CSRF token to your forms (as a hidden variable), that would be submitted with the request.
CSRF reference
There are many ways to generate the token, but essentially you:
create the token
store in your $_SESSION
output the form with <input type="hidden" name="{token name}"
value="{token value}" />
Then when the form submits you check $_REQUEST['{token name}'] == $_SESSION[{token name}]`.
If that token is different you know it wasn't the form you originally generated and thus can ignore the request until the real form comes in with the correct token.
One thing: if an attacker can figure out how you generate your CSRF tokens then they can forge requests.
Added the below script after I login(say dashboard.php)
<script>
$(document).ready(function()
{
$("a").attr("target", "");
if(typeof(Storage) !== "undefined")
{
sessionStorage.pagecount = 1;
var randomVal = Math.floor((Math.random() * 10000000) + 1);
window.name = randomVal;
var url = "url to update the value in db(say random_value)";
$.post(url, function (data, url)
{
});
}
else
{
var url = "url to remove random_value";
$.post(url, function (data, url)
{
sessionStorage.removeItem('pagecount');
sessionStorage.clear();
window.location = 'logout.php';
});
}
});
</script>
Added the below script in Header in rest of my pages - 'random_value' is from db for that user
<script>
$(document).ready(function()
{
$("a").attr("target", "_self");
if(typeof(Storage) !== "undefined")
{
if (sessionStorage.pagecount)
{
if('<?=$random_value?>' == window.name)
{
sessionStorage.pagecount = Number(sessionStorage.pagecount) + 1;
}
else
{
var url = "url to remove random_value";
$.post(url, function (data, url)
{
sessionStorage.removeItem('pagecount');
sessionStorage.clear();
window.location = 'logout.php';
});
}
}
else
{
var url = "url to remove random_value";
$.post(url, function (data, url)
{
sessionStorage.removeItem('pagecount');
sessionStorage.clear();
window.location = 'logout.php';
});
}
}
else
{
var url = "url to remove random_value";
$.post(url, function (data, url)
{
sessionStorage.removeItem('pagecount');
sessionStorage.clear();
window.location = 'logout.php';
});
}
});
</script>
If I were doing this now, I would probably code a single page AngularJs app (although any form of Js will do).
On start-up, look in local storage for a flag. If set, refuse to start, with suitable message, else set the flag & run the app.
Sure, a malicious user could get around it, since it's not a server-side check, but I would just refuse to support such.
It seems there are some very good resources on my question - at least the basics. But there are still a few dark areas for myself.
First - What I am trying to do.
I am having users "wait" at an intermediary page while I check to see if they have submitted the correct information from another resource (ie. SMS or email) to confirm their subscription to my service. The page should asynchronously check ever couple of seconds to see if they have completed the required steps and their status has been set to '1' or 'active' in the database.
Completing the ajax request seems pretty straightforward - the timing however, I am a little thick about.
Also, what is it that I am trying to do with the information I am retrieving - great, so the user has been set to 'active' and I can retrieve that value - but then what do I use to redirect them to the final or 'thank you' page? Is it a javascript variable that should hold the value then redirect?
Sorry if this seems a little discombobulated, but this is my first try at Ajax & timed refreshes/responses.
I'd do a predefined number of intervals on the ajax call - after 3 attempts, fail with a message:
var attempts = 0;
var validation_attempt = setInterval(function(){
if(attempts > 3)
{
clearInterval(validation_attempt);
$('#my_div').html('No activity, try again later');
return false;
}
$.ajax({
url: '/my_file.php',
/* other params */
success:function(data){
if(data == 'ok')
{
clearInterval(validation_attempt);
$('#my_div').html('Your account has been approved');
}
else
{
attempts++;
}
}
});
},3000); // 3 seconds
This should essentially let the user wait for 9 seconds at the most before seeing a "No activity, try again later" message.
Your "waiting" page can poll with ajax for completion. It might poll every 15-20 seconds initially, back up to once a minute after a little while and then stop polling all together after awhile. In all cases, make sure you don't keep polling forever as this will be bad for your backend if the user never completes their end of the subscription process.
If the ajax polling gets a successful answer, I would think you would then redirect the user to one of two places:
1) A page that just indicates "Success - you are now subscribed" and tells them what to do next.
or
2) The opening web page for actually using the service. This may be a login page or perhaps you can already log them in automatically so you take them to the open page for the service.
You can do this redirect either in client-side javascript (e.g. it already knows where to redirect the user in all scenarios) or you can include the redirection URL in that actual ajax response so the flow of pages can be controlled server-side. You can do it either way.
I was coding this in response to your question. I think I'll post it in case it is useful:
var active = false, i, t;
// check 'active' for truthyness, if so kill timers
function checkActive() {
if(active) {
clearInterval(i);
clearTimeout(t);
window.location.href = "success.html";
}
return false;
}
// recursively call URL to check 'active' status
function checkStatus() {
$.post("checkStatus.php", { some: "variable" }, function(resp) {
if(resp !== "1") {
t = setTimeout(checkStatus, 2000);
} else {
active = true;
}
});
}
$(document).ready(function() {
checkStatus();
i = setInterval(checkActive, 2000);
});
You can try using a server push (comet) rather than ajax for this solution.
A quick question of perhaps a more speculative nature. I've been getting heavy into jquery lately to handle all the ajax in my web apps.
Right now I'm building a bidding system in PHP that makes heavy use of mod_rewrite. I'm using jQuery with a confirm dialog to send an Ajax request that will spend some of the user's predeposited credits on a bid. The ajax request to spend is sent with the cost as a post parameter to a PHP controller that spends the users credits and then echos the output, which jQuery then places back into the document.
It's working fine, but what I'm wondering is if there is a better way to make jQuery handle the refusal of the purchase if the user has insufficient credits. Right now I have the php answering with an echo that displays this message with a link to the make a deposit page... but I'd rather have a redirect happen automatically.
Is there some way my jQuery script could be notified with the boolean of success or failure before .load finishes, and then redirect in the case of failure? Possibly through HTTP headers determining the handling? The only way I could think of is to place a true or false in an html element that gets check in the callback after .load() and in the case of a false perform a redirect.
Thanks and sorry for the explanation length.
If every bidding attempt requires going somewhere else, why use AJAX in the first place?
Anyway, if you look at jQuery's API documentation you'll see that load() is not the only function available. Most of the are simplified versions of ajax(); if you use this one, you can control all possible events right from there. No need to mess with HTTP headers!
I suggest you redesign your server-side script so it returns a JSON object. Then, you can send back all the different types of responses:
{
biddingSuccessful: true,
linkToDepositPage: "http://example.com",
textToDisplay: "Your bidding was successful"
}
Use the lower level $.ajax call to have full maximum control over the request. Ideally, instead of sending a success 2xx response, send an error response which will automatically get sent to your error callback.
$.ajax({
url: '..',
success: function() {
// if it gets here, then assume credits were used
},
error: function() {
// some error happened
// if error was about insufficient funds, then redirect
}
});
From the server, send the success response as you are doing right now. However for errors, change the response header, and send a JSON object or plain text indicating what the error was. And there is a header for exactly what you are looking for. It's 402 - Payment Required :)
header('HTTP/1.1 402 Payment Required');
send a JSON object as response with more details:
{
status: 'error',
reason: 'Insufficient Funds',
balance: '$2.78',
amountRequested: '$3.50'
}
For the updated comment, you need to use a closure (better than global variables :)
Suppose the outer function gets the element name/ID, wraps that value through a closure in the success callback. Please let me know if this is not what you intended.
function makeAJAXCall(elementName) {
$.ajax({
success: function(...) {
// elementName is available here through a closure
alert(elementName);
}
});
}
if the callback function is like "function (data) {" then you could return "NSF" or something like that, and simply compare 'data' to 'NSF', and do the redirection with window.location