Correct way of implementing Sessions and Cookies in PHP? - php

I'm simple not able to handle the correct way of implementing Cookies and Sessions. I have everytime another error when I think I implemented the code properly.
When the user register or login I run this code:
session_start();
$_SESSION['SESS_USER_ID'] = $user_id;
$_SESSION['SESS_USER_NAME'] = $user_username;
$_SESSION['SESS_USER_EMAIL'] = $user_email;
$_SESSION['SESS_LOGGED'] = TRUE;
session_write_close();
setcookie("COOK_USER_ID",$user_id,time()+(3600*24*365));
setcookie("COOK_USER_NAME",$user_username,time()+(3600*24*365));
setcookie("COOK_USER_EMAIL",$user_email,time()+(3600*24*365));
setcookie("COOK_LOGGED",TRUE,time()+(3600*24*365));
On every page I want to protect I run this code:
session_start();
if(isset($_SESSION['SESS_LOGGED']) && isset($_COOKIE['COOK_LOGGED']))
{
}
elseif (isset($_COOKIE['COOK_LOGGED'])) {
$_SESSION['SESS_USER_ID'] = $_COOKIE['COOK_USER_ID'];
$_SESSION['SESS_USER_NAME'] = $_COOKIE['COOK_USER_NAME'];
$_SESSION['SESS_USER_EMAIL'] = $_COOKIE['COOK_USER_EMAIL'];
$_SESSION['SESS_LOGGED'] = TRUE;
session_write_close();
}
else {
include_once("register.php");
exit();
}
And when the user logs off I run this code:
if (isset($_GET['logout'])) {
//Destroy Session
session_start();
$_SESSION = array();
session_unset();
session_destroy();
header("Location: index.php");
//Unset the Cookies
if (isset($_SERVER['HTTP_COOKIE'])) {
$cookies = explode(';', $_SERVER['HTTP_COOKIE']);
foreach($cookies as $cookie) {
$parts = explode('=', $cookie);
$name = trim($parts[0]);
setcookie($name, '', time()-1000);
setcookie($name, '', time()-1000, '/');
}
}
exit();
}
But when I close the browser and start it again, there is always a weird behavior where the cookies are not set properly etc.
I don't know what I'm doing wrong. On all pages I run at first ob_start(); so the problem can't be a header related one.

Related

Can't access session data session_write_close

I was having issues with the session file being locked, so I added session_write_close() once I was done with the session. The script worked properly before that, however, once I leave the sign-in page now, the session is blank.
Session is started at the top of index.php which includes the sign in page:
$result = 'token_valid';
$_SESSION['user'] = $email;
print_r($_SESSION);
session_write_close();
print_r($_SESSION);
The session data is returned properly both times on the sign-in page.
Array ( [user] => abc#gmail.com ) Array ( [user] => abc#gmail.com )
A link returns to the home page, which calls a function to check if logged in...
function user_is_signed_in() {
print_r($_SESSION);
session_write_close();
if($user == '') {
return False;
}
else {
return True;
}
}
The session no longer has any data.
Full index.php
<?php
session_start();
include_once('fnc/database.php');
include_once('fnc/user.php');
if(!user_is_signed_in()) {
include('sign-in.php');
}
else {
$url = parse_url($_SERVER['REQUEST_URI']);
if(!$url['query'])
{
include('home.php');
}
else {
if(isset($_GET['media']))
{
include($_GET['media'].'.php');
}
if(isset($_GET['user']))
{
include($_GET['user'].'.php');
}
}
}
.
.
Workaround (probably filthy)
Issue seems to be caused by the reading/writing of the actual session file. Used the session_id generated by PHP and just created a secondary session file. Do not save in same folder (or if you do, change the filename) - session_start seems to delete and regenerate the session file PHP manages and you'll lose any data written there.
session_start();
$sess = array();
$sess = $_SESSION;
$sess["id"] = session_id();
//print_r($sess);
session_write_close();
Create session_data in session folder
$session_details = "user|".$email;
$session_file = "/Programs/XAMPP/tmp/session_data/sess_".$sess["id"];
//echo $session_details;
$fh = fopen($session_file, 'w+');
fwrite($fh, $session_details);
fclose($fh);
Read session data from this file instead of the session
$session_path = "/Programs/XAMPP/tmp/session_data/sess_".$sess["id"];
$fh = fopen($session_path, 'r');
$session_file = fread($fh, filesize($session_path));
$exploded_session = explode("\n", $session_file);
$session_data = array();
foreach($exploded_session as $line)
{
$tmp = explode("|", $line);
$session_data[$tmp[0]] = $tmp[1];
}
return $session_data["user"];
fclose($fh);

Session ID regeneration not working on windows

I have the following code which works fine under several linux flavors I have tried (Ubuntu, Debian 8, CentOS 7), however when I use it on windows, the regeneration fails without an error. $duration is a value in the class represented as static $duration = 60 * SESSION_TIMEOUT; where SESSION_TIMEOUT is a constant defined in a config.inc.php file (user setting).
session_start();
self::csrf(false);
if(self::verify(false) === true) {
$_SESSION['expires'] = time() + self::$duration;
}
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
If I echo $id after the line $id = session_id(); there is a value, however if I echo session_id() after the last session_start(), it is empty. (See session_regenerate_id if you think this method is 'Dodgy' )
I do not know why this regeneration code is failing :
session_regenerate_id(true);
$id = session_id();
// one of the lines below this are causing session_id() to be blank
session_write_close();
session_id($id);
session_start();
Please assist me in identifying what is causing the new session to be blank when it should contain the new session id.
session.save_path = 2;755;d:/server/www/127.0.0.1/sessions
Update: I was able to find this in the log, however the interesting part is the session file is created with data.
PHP Warning: Unknown: Failed to write session data (files). Please verify that the current setting of session.save_path is correct (2;755;d:/server/www/127.0.0.1/sessions) in Unknown on line 0
As requested, a sample of $id before calling session_write_close() (and subsequent code), is 9vdom0ghuqkvsdcnkjacurc6rf2n4fn1s1gvfva44okd15jdpm30
Update:
After some more testing, I have traced the problem to be a bit more complex than stated above. The problem I initially had which I believed was a session regeneration issue, is more of a problem where session variables are not being stored. I believe this is still related to session regeneration somewhat on windows, however I still am unable to pinpoint a fix, as this code works beautifully on Linux.
Here is the class (minified):
class session {
static $duration = 60 * SESSION_TIMEOUT;
public static function start() {
session_start();
self::csrf(false);
if(self::verify(false) === true) {
$_SESSION['expires'] = time() + self::$duration;
}
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
}
public static function csrf($new = false) {
if(!isset($_SESSION['csrf']) || $new === true) {
$_SESSION['csrf'] = sha1(openssl_random_pseudo_bytes(mt_rand(16,32)));
}
return $_SESSION['csrf'];
}
}
The code used to call it, is rather complex (when it comes to determining when to generate a new csrf token), but below is my attempt to extract a chunk from my overall project to demonstrate the usage. The login code behind should suffice I believe to view the logic:
if( session::verify() === true ) {
redirect();
} else {
$error = '';
$token = $_SESSION['csrf'];
if ( !empty($_POST) ) {
if ( isset($_POST['csrf']) ) {
// check CSRF token and if match ti token stored in session
$csrf = trim(filter_input(INPUT_POST, 'csrf', FILTER_SANITIZE_SPECIAL_CHARS, array('flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH)));
if($csrf != $_SESSION['csrf']) { session::destroy(); $error = 'CSRF Attack Detected'; }
if( isset($_POST['username']) && isset($_POST['password']) && empty($error) ) {
// login validation code
if($auth) {
// regenerate csrf token
$token = session::csrf(true);
session::create( $username );
// redirect back to application root
redirect();
}
$error = 'Invalid credentials';
} else {
if(empty($error)) { $error = 'Invalid credentials'; }
}
// user was not authenticated, regenerate csrf token to prevent form spam
$token = session::csrf(true);
} else {
// CSRF token did not match stored token in session
$error = 'CSRF Attack Detected';
}
}
}
The additional methods for my session class as used in the login code are as follows :
Update session data with user information.
public static function create($user) {
$_SESSION['nonce'] = sha1(microtime(true));
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['agent'] = sha1($_SERVER['HTTP_USER_AGENT']);
$_SESSION['expires'] = time() + self::$duration;
$_SESSION['user'] = $user;
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
}
Verify posted information matches server session
public static function verify($destroy = false) {
$valid = true;
try {
if( !isset($_SESSION['nonce']) ) { $valid = false; }
if( !isset($_SESSION['user']) ) { $valid = false; }
if( isset($_SESSION['ip']) ) { if($_SESSION['ip'] != $_SERVER['REMOTE_ADDR']) { $valid = false; } } else { $valid = false; }
if( isset($_SESSION['agent']) ) { if($_SESSION['agent'] != sha1($_SERVER['HTTP_USER_AGENT']) ) { $valid = false; } } else { $valid = false; }
if( isset($_SESSION['expires']) ) { if($_SESSION['expires'] <= time()) { $valid = false; } } else { $valid = false; }
} catch (Exception $e) {
$valid = false;
}
if($valid === false) {
if(isset($_SESSION['nonce'])) { unset($_SESSION['nonce']); }
if(isset($_SESSION['ip'])) { unset($_SESSION['ip']); }
if(isset($_SESSION['agent'])) { unset($_SESSION['agent']); }
if(isset($_SESSION['expires'])) { unset($_SESSION['expires']); }
if(isset($_SESSION['user'])) { unset($_SESSION['user']); }
if($destroy === true) {
session_unset();
session_destroy();
}
}
return $valid;
}
Update:
Narrowed this down by running a simple test :
// ensure sessions are writeable
if(!is_writable(session_save_path())) {
// try alternate session path
$_session_save_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'sessions';
if(!is_writable($_session_save_path)) {
echo "Can't write sessions"; exit;
} else {
session_save_path(
$_session_save_path
);
}
}
Which echo's the result 'Can't write sessions' meaning it failed all methods to try and write data.
What is interesting here, is that no matter what I do (even setting the root of the drive to 'Everyone' with 'Full' permissions, all subfolders and files seem to have a permanent semi-shaded 'Read Only' checkbox. Beyond this, file_put_contents("hello.txt", "hello"); exit(); works in the root folder of my local site, but not from any subfolder below 2 directories deep -- even after assigning (and checking) permissions. (e.g. d:\server\websites\127.0.0.1\htdocs\ works, but not d:\server\websites\127.0.0.1\htdocs\path1\path2\
Update:
Did some more troubleshooting, and the rabbit hole gets deeper. Ran a script to find what user PHP is running under to better assist in checking permissions:
echo 'Current script owner: ' . get_current_user();
which echo's 'Current script owner SYSTEM'
I followed up with this code :
$new = false;
session_start();
if(!isset($_SESSION['csrf']) || $new === true) {
$_SESSION['csrf'] = sha1(openssl_random_pseudo_bytes(mt_rand(16,32)));
}
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
Which still failed. I did try something which finally worked, but I really don't feel comfortable with it as it involves not setting the security bits as documented in the php.ini file.
I changed :
session.save_path = "2;755;d:/server/www/127.0.0.1/sessions"
To
session.save_path = "d:/server/www/127.0.0.1/sessions"
And this works, however all the session files are now world accessible.
(Server is the same version of apache and php on all platforms configured with identical php.ini files, and server configuration files as close as possible)

PHP Destroy session on isset

I am trying to destroy a session when a session is selected but it is not being destroyed:
if (isset($_POST['primary_cat'])) {
$_SESSION['primary_cat'] = $_POST['primary_cat'];
unset($_SESSION['secondary_cat']);
}elseif(empty($_SESSION['primary_cat'])) {//define primary_cat
$_SESSION['primary_cat'] = null;
}
When I change $_POST['primary_cat'] this is changed but $_SESSION['secondary_cat'] is not being destroyed. How can I destroy $_SESSION['secondary_cat']
This is how I completely destroy the one and only session I have:
session_start();
$_SESSION = array();
if (isset($_COOKIE[session_name()])) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000, $params["path"],$params["domain"], $params["secure"], $params["httponly"]);
echo " Zerstöre Cookie... ";
}
#session_unset();
#session_destroy();
Maybe it helps you to adapt your code for your specific session.
if (isset($_POST['primary_cat'])) {
$_SESSION['primary_cat'] = $_POST['primary_cat'];
unset($_SESSION['secondary_cat']);
} elseif (empty($_SESSION['primary_cat'])) {//define primary_cat
$_SESSION['primary_cat'] = null;
}
You should try these instead:
if (isset($_POST['primary_cat'])) {
session_destroy();
$_SESSION['primary_cat'] = $_POST['primary_cat'];
} else if (!$_SESSION['primary_cat']) {
//your business
}
An explanation to that is on clicking or selecting
"primary_cat"
it should run that block of code else it won't run that code and if it does, you the
session_destroy();
Destroys active session and the below creates a new session due your specifications.
Your code seems ok, problem might be from your browser.
make sure session is started. if it's still not destroyed, then close your browser and restart apache.
But also make sure you are not setting $_SESSION['secondary_cat'] somewhere else your code.
To be sure, do the following after unsetting $_SESSION['secondary_cat']
if(isset($_SESSION['secondary_cat'])){
echo '<script type="text/javascript">alert("the session still has value : '.$_SESSION['secondary_cat'].'");</script>';
}
else
echo '<script type="text/javascript">alert("session has been unset");</script>';
The above will display a javascript alert showing "the session still has value : thevalue" if the session was not unset or "session has been unset" if it really has been unset
hope this helps
try this...
if(isset($_SESSION['secondary_cat'])&&!empty($_SESSION['secondary_cat'])){
unset($_SESSION['secondary_cat']);
}

Can I open/close a session in PHP multiple times (and if so what is going wrong)?

So this is how my login process works:
authenticate.php
sessionStart();
if (isset($_SESSION) && !empty($_SESSION['LOCATION'])) {
$location = $_SESSION['LOCATION'];
unset($_SESSION['LOCATION']);
} else {
$location = '//' . $_SERVER['SERVER_NAME'];
}
session_write_close();
sessionStart();
$userIsOnline = isset($_SESSION['ID']);
session_write_close();
sessionStart();
if (!$userIsOnline) {
// Get the user from the database
// Validate the user's password
$_SESSION['ID'] = $user->id;
$_SESSION['UN'] = $user->un;
// ... more information
}
session_write_close();
header($location);
exit();
The contents of the sessionStart function:
if (session_id() == '') {
session_name('MyWebsite');
session_set_cookie_params(86400, '/', $_SERVER['SERVER_NAME'], true, true);
session_start();
$_SESSION['LAST_ACTIVITY'] = time();
$_SESSION['CREATED'] = time();
}
Then on the top of every page on my website:
sessionStart();
print_r($_SESSION);
$_SESSION['LOCATION'] = $_SERVER['REQUEST_URI'];
session_write_close();
Prints an empty array. So for some reason, it is wiping my session array during the redirect? Anyone have any ideas?
Also, the values of CREATED and LAST_ACTIVITY are from this question.
If it is not the issue that HTTPS is not used, but the session cookie is set to Secure then my other thought is to change
if (session_id() == '') {
session_name('MyWebsite');
session_set_cookie_params(86400, '/', $_SERVER['SERVER_NAME'], true, true);
to
if (session_name('MyWebsite') != 'MyWebsite') {
session_set_cookie_params(86400, '/', $_SERVER['SERVER_NAME'], true, true);
I wonder if it is giving you a Session ID under a different name, which is why print_r($_SESSION); is coming up empty. If not, I'm out of ideas!

Detect if cookies are enabled in PHP

I am trying to detect if a user on my page has cookies enabled or not. The following code performs the check, but, I have no idea on how to redirect the user to the page they came from.
The script starts a session and checks if it has already checked for cookies. If not, it redirects the user to a test page, and since I had called session_start() in the first page, I should see the PHPSESSID cookie if the user agent has cookies enabled.
The problem is, ths script might be called from any page of my site, and I will have to redirect them back to their selected page, say index.php?page=news&postid=4.
session_start();
// Check if client accepts cookies //
if (!isset($_SESSION['cookies_ok'])) {
if (isset($_GET['cookie_test'])) {
if (!isset($_COOKIE['PHPSESSID'])) {
die('Cookies are disabled');
} else {
$_SESSION['cookies_ok'] = true;
header(-------- - ? ? ? ? ? -------- -);
exit();
}
}
if (!isset($_COOKIE['PHPSESSID'])) {
header('Location: index.php?cookie_test=1');
exit();
}
}
I think its better to make one file set cookie and redirect to another file. Then the next file can check the value and determine if cookie is enabled. See the example.
Create two files, cookiechecker.php and stat.php
// cookiechecker.php
// save the referrer in session. if cookie works we can get back to it later.
session_start();
$_SESSION['page'] = $_SERVER['HTTP_REFERER'];
// setting cookie to test
setcookie('foo', 'bar', time()+3600);
header("location: stat.php");
and
stat.php
<?php if(isset($_COOKIE['foo']) && $_COOKIE['foo']=='bar'):
// cookie is working
session_start();
// get back to our old page
header("location: {$_SESSION['page']}");
else: // show the message ?>
cookie is not working
<? endif; ?>
Load cookiechecker.php in browser it'll tell cookie is working. Call it with command line like curl. It'll say, cookie is not working
Update
Here is a single file solution.
session_start();
if (isset($_GET['check']) && $_GET['check'] == true) {
if (isset($_COOKIE['foo']) && $_COOKIE['foo'] == 'bar') {
// cookie is working
// get back to our old page
header("location: {$_SESSION['page']}");
} else {
// show the message "cookie is not working"
}
} else {
// save the referrer in session. if cookie works we can get back to it later.
$_SESSION['page'] = $_SERVER['HTTP_REFERER'];
// set a cookie to test
setcookie('foo', 'bar', time() + 3600);
// redirecting to the same page to check
header("location: {$_SERVER['PHP_SELF']}?check=true");
}
HTTP_REFERER did not work for me, seems like REQUEST_URI is what I need.
Here is the code I finally used:
session_start();
// ------------------------------- //
// Check if client accepts cookies //
// ------------------------------- //
if( !isset( $_SESSION['cookies_ok'] ) ) {
if( isset( $_GET['cookie_test'] ) ) {
if( !isset( $_COOKIE['PHPSESSID'] ) ) {
die('Cookies are disabled');
}
else {
$_SESSION['cookies_ok'] = true;
$go_to = $_SESSION['cookie_test_caller'];
unset( $_SESSION['cookie_test_caller'] );
header("Location: $go_to");
exit();
}
}
if( !isset( $_COOKIE['PHPSESSID'] ) ){
$_SESSION['cookie_test_caller'] = $_SERVER['REQUEST_URI'];
header('Location: index.php?cookie_test=1');
exit();
}
}
// ------------------------------- //
There's no need to save the original URL and redirect to it afterwards. You can perform a transparent redirect via AJAX which doesn't trigger a page reload. It's very simple to implement. You can check my post here: https://stackoverflow.com/a/18832817/2784322
I think this is easiest solution. Doesn't require separate files and allows you to proceed with script if cookies are enabled:
$cookiesEnabled = true;
if (!isset($_COOKIE['mycookie'])) {
$cookiesEnabled = false;
if (!isset($_GET['cookie_test'])) {
setcookie('mycookie', 1, 0, '/');
#ob_end_clean();
$_SESSION['original_url'] = $_SERVER['REQUEST_URI'];
$uri = $_SERVER['REQUEST_URI'];
$uri = explode('?', $uri);
$q = (isset($uri[1]) && $uri[1])?explode('&', $uri[1]):array();
$q[] = 'cookie_test=1';
$uri[1] = implode('&', $q);
$uri = implode('?', $uri);
header('Location: '.$uri);
die;
}
} else if (isset($_GET['cookie_test'])) {
#ob_end_clean();
$uri = $_SESSION['original_url'];
unset($_SESSION['original_url']);
header('Location: '.$uri);
die;
}
// if (!$cookiesEnabled) ... do what you want if cookies are disabled

Categories