How to restrict my app to a single browser tab? - php

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.

Related

Should I use $_GET to dynamically load html?

I am working on a social networking site using PHP, MySQL(PDO), JQuery, and HTML(CSS). I'm working on the profile page right now and I want to determine if the user is on their own page or another's to determine what inputs they'll have. I would like to keep a framework for the profile page and load the respective options rather than having 2 different pages (my profile vs other profile).
I'm learning JQuery and PHP as I go, so right now I am thinking, in JQuery, I can get the $_GET (?username= ) and compare it to the user id of who's logged in, then load the components I need based on that.
Or I determine the user in PHP then run a certain scrip to load the components.
Would on of these ways work, or is there a different/better way to do this?
I don't think you can use $_GET in JavaScript.
Instead, there is a URLSearchParams
const url = new URL('http://domain/path?username=someone');
const username = new URLSearchParams(url.search).get('username)'; // someone
The best security practice is to not rely on anything happening in browser.
This means you should always figure out privileges on the server (on PHP side).
Variant 1 (Simple)
Let's assume you have authenticated the user and have the user as $currentUser and their id as $currentUser->id in PHP.
Now this user wants to view some profile by requesting url "profile?id=555"
So here's (basically) what you do on PHP side:
$requestedProfile = (int)$_GET['profile'];
if ($requestedProfile == $currentUser->id) {
// they want own profile
show_controls_for_own_profile();
} else {
// they want someone else's profile
show_controls_for_other_profile();
}
In this case, jQuery has nothing to do here.
Variant 2 (Separate requests)
Further, let's assume you want to cache the entire profile page to quickly load it into browser, and only after that, ajax-load additional controls associated with user's privileges.
then you have two controller methods (php scripts, whatever) each serving it's own part:
public function showProfile() {
$requestedProfile = (int)$_GET['profile'];
if (profile_exists($requestedProfile)) {
show_the_profile($requestedProfile); // no check here
}
}
The method above would return the generic profile page by its ID (with empty ".controls" element) and on that page, some jquery code would ask the server to return appropriate variant of user-dependent part.
$.ajax('/user-controls.php')
.done(function(response) { $('.controls').html(response); });
Notice it does not tell the server any user ID - server should already know current authenticated user ID from the session!
The second function, same as in the beginning, will return just HTML for the controls:
// Example of getting current profile from server's headers
function get_viewed_profile_id()
{
// url of previous profile page, e.g. http://example.com/profile?id=555
$referer = $_SERVER['HTTP_REFERER'];
$query = parse_url($referer, PHP_URL_QUERY); // id=555
parse_str($query, $params); // ['id' => '555']
return $params['id']; // 555
}
// we should store the authenticated user in session,
// not rely on anything user sends from their browser
$currentUserId = $_SESSION['user']->id;
$requestedProfileId = get_viewed_profile_id();
// now that we know both viewed profile and current user,
// we can compare them
if ($requestedProfileId == $currentUserId) {
// they want own profile
show_controls_for_own_profile();
} else {
// they want someone else's profile
show_controls_for_other_profile();
}
Variant 3 (Passing PHP variable to javascript)
Same as above, but you don't rely on $_SERVER['HTTP_REFERER'], and move some logic to Javascript instead:
Browser asks:
// look up currently browsed profile from current window url
var q = new URLSearchParams(window.location.search);
var controlsURL = '/user-controls.php?viewedProfile=' + q.get('id');
// ask for controls, specifying the currently browsed profile
$.ajax(controlsURL)
.done(function(response) { $('.controls').html(response); });
And server responds with:
function get_viewed_profile_id()
{
return (int)$_GET['viewedProfile'];
}
// we should store the authenticated user in session,
// not rely on anyhting user sends from their browser
$currentUserId = $_SESSION['user']->id;
$requestedProfileId = get_viewed_profile_id();
// now that we know both viewed profile and current user,
// we can compare them
if ($requestedProfileId == $currentUserId) {
// they want own profile
show_controls_for_own_profile();
} else {
// they want someone else's profile
show_controls_for_other_profile();
}
in jquery you can use $.get() with datatype html
You can echo the PHP's GET value in JavaScript. But make sure, without makeItSafe (which you have to define yourself) this code can be exploited to perform cross site scripting attacks:
<script>
var myGlobalUserID = "<?php echo makeItSafe($_GET['userID']); ?>";
</script>
Simply declare one hidden field on page
<input type="hidden" id="username" value="<?php echo $_GET['username'];?>">
Get username value in it.
Then simply fetch that value in jquery
<script>
$(document).ready(function(){
var username = $("#username").val();
if(username == "abc"){
$("#div_one").show();
$("#div_two").hide();
}
});
</script>
using if() condition and hide() , show() method load component which you want to show to user.

Why is php session data not saved when I post data at almost the same time an ajax get request is executed?

Using a html element for search, my search allows GET and POST requests. I am sending the pressed keys using ajax GET for live search (timeout 750ms) and when the form is submitted, the string is POSTED.
The problem is that new session data is not saved when the ajax request and the form is submitted at the same time.
I found two solutions to this problem but they seem to mask the real problem. First solution is to stop the ajax request when enter is pressed using javascript if ((event.keyCode || event.which) == 13) and 2nd solution is calling session_write_close() and session_start() right after new session data is saved.
Can you explain why some session data is not saved (while others are properly saved during same request) when ajax GET is executed while a html form is in the middle of being posted or else explain the need for calling session_write_close and session_start to make sure session data is saved during critical operations like checking CSRF: generate new token if post is valid and save this in session?
Example code:
PHP Wrapper for storing new key:
public function setCsrfKey($property)
{
$_SESSION['x']['CsrfKey'] = (string) $property;
}
JS code:
$(searchBox).on("keyup", function(e){
e.stopPropagation();
clearTimeout(ajaxTimeOut);
var keyPressed = e.keyCode || e.which;
var string = $(this).val();
if (string.length < 3) {
queueList.clearQueue('ajaxCall');
return;
}
queueList.queue('ajaxCall', function(){
$.ajax({
url: urlString,
type: 'GET',
}).done (function( data ) {
}).fail (function(){
});
});
ajaxTimeOut = setTimeout( function(){
while (queueList.queue('ajaxCall').length > 1) {
queueList.queue('ajaxCall').shift();
}
queueList.dequeue('ajaxCall');
}, 750);
});
PHP writes session data when the script ends unless you tell it otherwise (with session_write_close). What happens with two simultaneous requests in the same session depends on your session handler...but if the request is allowed to happen, the two requests typically won't see each other's changes to the session, and one's changes will generally get lost.
Calling session_write_close saves the session so that future requests will use the updated session. It's a bit of a hack; what you have here is a race condition, and there's still a chance of stuff breaking. It'll just be a lot lower the sooner you can commit your changes to the disk/database/whatever. Lower still if you can insert a short delay between requests as well.
Of course, once you've closed the session, it won't get saved when the script ends, so nothing else will get added to it. You have to call session_start() to reopen it if you want to make further changes to it.

Can I save serialized form data?

In my continued effort to understand hash tags and page navigation I'm hitting a road block with resubmitting form data if a user uses the browsers navigation buttons (e.g. back and forward).
My function that uses the form data is below:
if(page == "/visits_results") {
$('#start').val(start);
$.post("visits_results.php", $("#profile_form_id").serialize(),
function(data) {
$('#search_results').html(data);
location.href = "#visits_results=" + start;
});
}
This works fine and dandy if the form is still visible, for instance if I use pagination it performs as I would expect.
My issue is when the user clicks the browsers back button (the form has now been removed) and then they click the browsers forward button. The event gets triggered but my serialized form data is now empty. Is there any way to cache the form data so I can continue to call it?
Any thoughts?
Your best bet (I think) would be to store the serialized data in a cookie. When the user returns to the page which he/she has already filled out, retrieve the data from the cookie, unserialize it, and place it back where it belongs.
Cookies are fairly trivial to work with, there is a decent cross-browser implementation for reading/writing cookies on quirksmode:
http://www.quirksmode.org/js/cookies.html
or if you'd prefer to use a plugin:
http://code.google.com/p/cookies/
http://plugins.jquery.com/project/Cookie
You can save data fairly easily using localStorage like http://jsfiddle.net/zDPjm/
Well, sure, you could do something very simple such as:
if ($("#profile_form_id").serialize() != "") serializedFormData = $("#profile_form_id").serialize();
$.post("visits_results.php", serializedFormData, /* Rest of Code */

How to deal with session timeouts in AJAX requests

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.

How can web form content be preserved for the back button

When a web form is submitted and takes the user to another page, it is quite often the case that the user will click the Back button in order to submit the form again (the form is an advanced search in my case.)
How can I reliably preserve the form options selected by the user when they click Back (so they don't have to start from scratch with filling the form in again if they are only changing one of many form elements?)
Do I have to go down the route of storing the form options in session data (cookies or server-side) or is there a way to get the browser to handle this for me?
(Environment is PHP/JavaScript - and the site must work on IE6+ and Firefox2+)
I believe you're at the mercy of the browser. When you hit back, the browser does not make a new request to the server for the content, it uses the cache (in nearly every browser I've seen anyway). So anything server-side is out.
I'm wondering if you could do something very complicated like storing the search result in a cookie during the onunload event of the results page, and then reading the cookie in javascript on the search page and filling in the form - but this is just speculation, I don't know if it would work.
I'd put it in the session.
It's going to be the most reliable and even if they don't go straight "back", it'll still have their search options there.
Putting it in the cookie would also work, but wouldn't be recommended unless it's a very small form.
It's up to the browser, but in most cases you don't have to do anything.
IE, Firefox, etc. will happily remember the contents of the form in the previous page, and show it again when Back is clicked... as long as you don't do anything to stop that working, such as making the page no-cache or building the form entirely from script.
(Putting stuff in the session is likely to confuse browsers with two tabs open on the same form. Be very careful when doing anything like that.)
The problem you have is that the browser will return a cached version of the page, and probably not ask the server for it again, meaning using the session would be irrelevant.
You could however use AJAX to load the details of the previously submitted form on the page's load event.
You would basically have to store it on your server in some way (probably in session variables, as suggested) after the POST. You also have to setup Javascript on the form page to execute on load to issue an AJAX call to get the data from your server (in, say, JSON format) and prefill the form fields with the data.
Example jQuery code:
$( document ).ready( function() {
$.getJSON(
"/getformdata.php",
function( data ) {
$.each( data.items, function(i,item) {
$( '#' + item.eid ).val( item.val );
} );
});
} );
Your /getformdata.php might return data like:
{
'items': [
{
'eid': 'formfield1',
'val': 'John',
},
{
'eid': 'formfield2',
'val': 'Doe',
}
]
}
and it would obviously return an empty array if there were nothing saved yet for the session. The above code is rough and untested, but that should give you the basic idea.
As a side note: current versions of Opera and Firefox will preserve form field content when going Back. Your JS code will overwrite this, but that should be safe.
Another ajaxy options (so it's not good for users without javascript) is to submit the form via javascript and then go the confirmation page (or show the message on the form by replacing the button with a message). That way there is no "back" possible.
Why not store the values into a cookie before submitting? You could also include a timestamp or token to indicate when it was filled out. Then when the page loads, you can determine whether you should fill in the fields with data from the cookie, or just leave them blank because this is a new search.
You could also do everything local by javascript. So you store a cookie with the search value and on pageload you check if the cookie exists. Something like this:
$('#formSubmitButton').click( function() {
var value = $('#searchbox').val();
var days = 1;
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
document.cookie = "searchbox="+value+expires+"; path=/";
});
$(document).ready( function() {
var nameEQ = "searchbox=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) var valueC = c.substring(nameEQ.length,c.length);
}
$('#searchbox').val(valueC);
});
Have not tested this but is should work. You will need jQuery for this.
Cookie functions came from: http://www.quirksmode.org/js/cookies.html
I should say btw that both FireFox and IE have this behaviour by default.

Categories