I am currently working on a website and I am having hard time understand how can I make a secure and flexible system where I can manage user's logged in state.
I am aware of $_SESSION and $_COOKIE and I understand how they work, however, I can't find a way to use them properly.
Here is a piece of code that I built for keeping track of whether the user is signed in or not.
state_handler.php
include_once(__DIR__ . '/../setup.php');
include_once(COOKIE_UTIL_PATH);
include_once(SESSION_UTIL_PATH);
if (is_session_set() === FALSE) {
if (is_cookie_set() === TRUE) {
include_once(DB_PATH);
$data = get_cookie_data();
$user = select_user_by_username($data['username']);
if (!$user || strcmp($data['password'], $user['password']) !== 0) {
destroy_cookie();
header('Location: /index.php');
exit();
}
session_start();
set_session_params($user);
$new_data = array(
'username' => $user['username'],
'password' => $user['password']
);
create_cookie($new_data);
} else if (strpos($_SERVER['REQUEST_URI'], '/src/signin/signin.php') === FALSE) {
header('Location: /src/signin/signin.php');
exit();
}
} else if (strpos($_SERVER['REQUEST_URI'], '/src/signin/signin.php') !== FALSE) {
header('Location: /index.php');
exit();
}
cookie_util.php
function create_cookie(array $data): void
{
setcookie(
'user_information',
json_encode($data),
[
'expires' => time() + 1296000,
'path' => '/',
'httponly' => true,
'samesite' => 'Strict'
]
);
}
function is_cookie_set(): bool
{
return isset($_COOKIE['user_information']);
}
function get_cookie_data(): array
{
return json_decode($_COOKIE['user_information'], true);
}
function destroy_cookie(): void
{
setcookie(
'user_information',
'',
[
'expires' => time() - 1296000,
'path' => '/',
'httponly' => true,
'samesite' => 'Strict'
]
);
setcookie(
'PHPSESSID',
'',
time() - 1296000,
'/'
);
}
session_util.php
function set_session_params(array $params): void
{
if (!isset($_SESSION)) {
session_start();
}
$_SESSION['id'] = $params['id'];
$_SESSION['username'] = $params['username'];
$_SESSION['password'] = $params['password'];
$_SESSION['name'] = $params['name'];
$_SESSION['surname'] = $params['surname'];
$_SESSION['group_id'] = $params['group_id'];
$_SESSION['team_id'] = $params['team_id'];
$_SESSION['city_id'] = $params['city_id'];
}
function is_session_set(): bool
{
if (!isset($_SESSION)) {
session_start();
}
return isset($_SESSION['id']);
}
function destroy_session(): void
{
if (!isset($_SESSION)) {
session_start();
}
session_unset();
session_destroy();
}
Once user signs in, he is sent a cookie with his Username and Password(hashed with MD5).
This cookie will live for 30 days. If the session expires, I then extract data from the cookie and compare it with DB data. If it works, then I simply start another session and refresh cookie's lifetime.
However, after giving it a bit more thought, this solution is quite problematic.
I am storing user's credentials (doesn't matter hashed or not) in cookies, which I think is a safety hazard.
I have no way of terminating user's logged in state as an admin. Even if I delete him from DB the user will stay active until the session expires, which is not something I want.
What would be the correct way of solving this? I've been doing this for a little more than a month and don't have lots of experience with website development, so please, help me out here.
One of the most important rules in web programming is never trust any information sent by the client (browser).
Although you have code to set the cookie, the value you see on the next request is not under your control: there is nothing to stop the user pressing F12 in their browser, and setting the cookie themselves.
This is why the concept of sessions was invented: the cookie you send to the browser is just a random ID, and the browser can neither read nor write the actual data you've associated with that ID.
So:
Don't set a custom cookie, just use the session; PHP will manage the cookie for you
You don't need to store the user's password, hashed or otherwise, to know that they're logged in
Even once a user's logged in, you can check in the database as often as you like to see if they've been disabled; this might be every request, every 5 minutes, or every time they do something particularly dangerous
Some very rough code, which you should read as an example not just copy and paste might look like this:
session_start();
if ( isset($_POST['username']) && isset($_POST['password']) ) {
$user = select_user_by_username($_POST['username']);
if ( password_verify($_POST['password'], $user['password_hash']) ) {
$_SESSION['login_successful'] = true;
$_SESSION['login_expires_at'] = strtotime('+30 minutes');
$_SESSION['user_id'] = $user['id'];
}
}
if (
! $_SESSION['login_successful']
|| $_SESSION['login_expires_at'] < time()
|| user_is_disabled($_SESSION['user_id'])
) {
redirect_to_login_page();
exit;
}
// Extend the login to be 30 minutes from last activity, not 30 minutes from login
$_SESSION['login_expires_at'] = strtotime('+30 minutes');
Related
I am using PhpMyAdmin and a custom single-sign on (SSO) script for direct login into the interface. The SSO script is called by PHP, given a unique login token by my own system. This script looks up the unique id in my system in order to retrieve MySQL username and password and returns this back to PhpMyAdmin.
This is working so far, but my next goal is automatic logout after a certain amount of inactivity. Without SSO, deleting my browser cookies and clicking any link, I get to the login page with the message »Your session has expired. Please log in again.«. However, I'm not able to reproduce this behavior from within my SSO script.
This is my SSO script:
<?php
/**
* Session timeout in seconds.
*/
define('SESSION_TIMEOUT', 60);
/**
* #return array|null Returns an array with login credentials or null for no login.
*/
function get_login_credentials() {
parse_str($_SERVER['QUERY_STRING'], $query);
/* check for session activity (timeout) */
if (isset($_SESSION['ssoLastActivity']) && (time() - $_SESSION['ssoLastActivity']) > SESSION_TIMEOUT) {
$sessionExpired = true;
} else {
$sessionExpired = false;
}
if (isset($query['old_usr'])) {
/* logout and back to index page */
unset($_SESSION['ssoLastActivity']);
unset($_SESSION['ssoUser']);
unset($_SESSION['ssoPassword']);
header('Location: index.php');
exit;
}
if ($sessionExpired) {
unset($_SESSION['ssoLastActivity']);
unset($_SESSION['ssoUser']);
unset($_SESSION['ssoPassword']);
/******** POINT OF QUESTION ********/
/* I'm trying to give the same response as if the cookies were deleted.
I land on the login page as desired, however I'm missing the session
timeout message. */
header('Content-Type: application/json');
echo json_encode(['redirect_flag' => '1', 'success' => false, 'error' => '']);
exit;
/***********************************/
}
/* update session activity timestamp */
$_SESSION['ssoLastActivity'] = time();
if (!empty($_SESSION['ssoUser']) && !empty($_SESSION['ssoPassword'])) {
/* already logged in */
return [
$_SESSION['ssoUser'],
$_SESSION['ssoPassword'],
];
}
/* retrieve MySQL login credentials here and store them in $user and $password */
/* $user = ...; $password = ...; */
return [
$user,
$password,
];
}
Has anybody a solution for logout via my SSO script, that leads me to the login page with the message, that the session is expired?
UPDATE:
The issue seems to be connected to my PhpMyAdmin server configuration (/etc/phpMyAdmin/servers.ini.php in my case):
<?php
$cfg['Servers'] = array(
1 => array('auth_type' => 'signon', ..., 'SignonScript' => '/usr/share/phpMyAdmin/sso.php', 'SignonURL' => 'index.php?server=1'),
2 => array('auth_type' => 'cookie', ...)
);
I inspected the network request after my session timeout, and it turns out, that there's actually a request with ?session_expired=1 (which triggers the session timeout message) sent to server 1; because this script is returning null (no login), it redirects to the SignonURL index.php?server=1, omitting the extra session_expired query param.
I could extend this url by &session_expired=1, however this would also trigger the message on regular logout.
I'm open for any ideas to improve the behavior.
I am using Slim framework to build a REST API and this is a rough application that I am using for development purposes. I want to log user in and out , and I set the session variable to the user id. The user is able to login perfectly fine in rest API but the remote device doesnt recognize the session (which means my $SESSION['id'] is empty) where as I clearly started this session in my host rest service. Here is my code:
require 'lib/Slim/Slim.php';
use lib\Slim\Middleware\SessionCookie;
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim(
array(
'cookies.encrypt' => true,
'cookies.secret_key' => 'my_secret_key',
'cookies.cipher' => MCRYPT_RIJNDAEL_256,
'cookies.cipher_mode' => MCRYPT_MODE_CBC
)
);
$app->add(new \Slim\Middleware\SessionCookie(array(
'expires' => '20 minutes',
'path' => '/',
'domain' => '',
'secure' => false,
'httponly' => false,
'name' => 'slim_session',
'secret' => '',
'cipher' => MCRYPT_RIJNDAEL_256,
'cipher_mode' => MCRYPT_MODE_CBC
)));
$app->get("/login/:string", function($string) use ($app)
{
$input = json_decode($string);
try
{
if ($input->username && $input->password)
{
$user = Model::factory('Users')->where("username",$input->username)->where("password",md5($input->password))->find_one();
//$app->setCookie('user_id',$user->id);
session_cache_limiter(false);
session_start();
$_SESSION['id'] = $user->id;
$status = 'success';
$message = 'Logged in successfully.';
}
else
{
$status = false;
$message = 'Could not log you in. Please try again.';
}
}
catch (Exception $e)
{
$status = 'danger';
$message = $e->getMessage();
}
$response = array(
'status' => $status,
'message' => $message
);
$app->response()->header("Content-Type", "application/json");
echo json_encode($response);
});
$app->get("/logout",function() use ($app)
{
try {
unset($_SESSION['id']);
session_destroy();
session_start();
//$app->getCookie('user_id');
$status = 'success';
$message = 'You have been logged out successfully';
}
catch (Exception $e)
{
$status = 'danger';
$message = $e->getMessage();
}
$response = array(
'status' => $status,
'message' => $message
);
$app->response()->header("Content-Type", "application/json");
echo json_encode($response);
});
It is returning 'Logged in successfully' but isn't actually logging me in so in my application when I check isset($_SESSION['id']) , there is nothing in the variable. Does anyone know whats going on? I am really confused because according to the slim documentation , it says :
The session cookie middleware will work seamlessly with the $_SESSION superglobal so you can easily migrate to this session
storage middleware with zero changes to your application code.
If you use the session cookie middleware, you DO NOT need to start a
native PHP session. The $_SESSION superglobal will still be available,
and it will be persisted into an HTTP cookie via the middleware layer
rather than with PHP’s native session management.
The issue would seem to be that you are not starting your session soon enough not anything with session middleware I would place session_start() at the top of the index
require 'lib/Slim/Slim.php';
use lib\Slim\Middleware\SessionCookie;
session_start();
Now it is started every time your application routes. So in login and logout remove your session_start() calls. Now in logout route redirect to your landing page or somewhere like:
$app->redirect('/yourawesomepage');
that recalls session_start() so you can remove that from here your logout route.
I'm having a strange behavior on my script. Using Facebook PHP SDK I create a button with a link to getLoginUrL() :
$loginUrl = $facebook->getLoginUrl(array(
'canvas' => 1,
'fbconnect' => 0,
'scope' => 'email',
'redirect_uri' => 'http://www.myurl.com')
);
Most of the time this works very well and I can access the user information using the following bit of code
if(!isset($_SESSION['front']['user_id']) || !is_numeric($_SESSION['front']['user_id']))
{
if (isset($_REQUEST['code'])) {
try {
$user_id = $facebook->getUser();
$basic = $facebook->api('/me?access_token=' . $access_token);
if (is_array($basic) && is_numeric($user_id)) {
$user = get_user_id_facebook($basic);
$_SESSION['front']['user_id'] = $user;
$_SESSION['front']['user_name'] = $basic['name'];
$_SESSION['front']['email'] = $basic['email'];
$_SESSION['front']['fbid'] = $basic['id'];
return true;
} else {
return false;
}
} catch (Exception $e) {
return false;
}
}
}
The problem is that sometimes I really cannot access the user information... why is this so unsteady? My php version is 5.3.2
Your code is unreliable as you're only executing the code if code exists in the GET or POST request. What you really should be doing is checking if the user is logged in, e.g.
if ( $facebook->getUser() != 0 ) {
// get profile information
} else {
// log user in
}
The code parameter won't always existing on your page, as it's returned by Facebook on successful login. But not when the user accesses your page directly or via bookmark.
I can't seem to get cookies set from my controller.
code:
/**
* #Route("", name="wx_exchange_default_index")
* #Template("WXExchangeBundle:Default:index.html.twig")
* #Method({"GET"})
*/
public function indexAction(Request $request)
{
$returnArray['parents'] = self::getCategories();
$cookieLocationSession = $request->cookies->get('locationidsession', 0);
$cookieLocation = $request->cookies->get('locationid', 0);
$securityContext = $this->container->get('security.context');
$hashids = $this->get("wxexchange_hashids_service");
if ($securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED'))
{
$user = $securityContext->getToken()->getUser();
$returnArray['user'] = $user;
$location = $user->getTblProfile()->getTblLocation();
if(!$cookieLocation || $cookieLocation != $hashids->encrypt($location->getId()))
{
$cookieLocation = $hashids->encrypt($location->getId());
//TODO: cookies are not being set figure out whys
$response = new Response();
$response->headers->setCookie(new Cookie("locationid", $cookieLocation, 365, '/', null, false, false));
$response->sendHeaders();
}
}
if (!$cookieLocation && !$cookieLocationSession)
{
return $returnArray;
}
if ($cookieLocationSession)
{
$cookieLocation = $cookieLocationSession;
}
if (!isset($location) || $cookieLocationSession)
{
$locationService = $this->get("wxexchange_location_service");
$locationId = $hashids->decrypt($cookieLocation);
if(count($locationId) >= 1)
$location = $locationService->getLocationById($locationId[0]);
}
if(isset($location))
return $this->redirect($this->generateUrl('wx_exchange_location', array('slug' => $location->getSlug(), 'locationid' => $cookieLocation)));
return $returnArray;
}
Do I have to return the response? If I do how do I keep processing (I have a redirect further down in my code)?
Edit: Interestingly enough if the same cookie is already set (via JQuery) running the code above deletes it, but it won't set it.
Edit: Action code posted.
The cookie object has httpOnly set to true by default, http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Cookie.html
This means that the browser should not make the cookie visible to client-side scripts. If you need to see the cookie in your scripts you can pass the 7th parameter as false when you create the cookie.
$response->headers->setCookie(new Cookie('foo', 'bar',time() + 60, '/', null, false, false));
If you just need to view the cookie for debugging purposes you can use Chrome Dev tools. They are available under the 'Resources' tab.
Edit : Try $response->sendHeaders();
Oops, Sorry everyone.
It's my error.
In my javascript I'm using a Jquery cookie plugin and when you set a new cookie you tell it the number of days before expiry:
$.cookie('locationid', value, { expires: 365, path: '/' });
Unfortunately I used a part of this syntax in my controller:
$cookie = new Cookie("locationid", $cookieLocation, 365, '/', null, false, false);
The problem is the third parameter is supposed to be a DateTime so while I thought I was telling it to expire in 365 days I probably created a cookie that expired almost instantly after creation.
Thanks for all the answers and time spent on your part, this is why I love SO.
I would like to dynamically set the session expiration time in Codeigniter. I'm autoload the session class. I have a view that contains checkbox for users to click (remember me). Right now if they click the check box or not the expiration time stays the same :/
// Config.php
$config['sess_expiration'] = 7200;
// Controller
if ($this->input->post('remember_me') == 'TRUE')
{
$this->session->remember_me();
}
$newdata = array(
'failed_login' => 0,
'user_name' => $this->input->post('user_name'),
'logged_in' => TRUE
);
$this->session->set_userdata($newdata);
// MY_Session.php
class MY_Session extends CI_Session {
function remember_me()
{
$this->sess_expiration = 172800;
}
}
If you need to implement "Remember me" feature - then you've started it in a wrong way.
You need to create one more database table with fields user_id | token.
Then, after user has been logged in (with "remember me" checkbox checked on) - generate random token and insert a new row with current user_id and that token. Also - set remember cookie with the same token value.
Now, if user enters your site, not authenticated and has some token - you can always find that token and authenticate user (each token is unique and strognly related to specific user_id).
Here is my solution I've been playing with.
You can edit the function inside domain.com/system/libraries/Session.php
function _set_cookie($cookie_data = NULL)
Comment out
// $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
Then Add
if(isset($this->userdata['rememberme'])){
// If they want to stay connected
if( $this->userdata['rememberme'] == 1) {
$expire = 0;
} else {
$expire = time() + $this->sess_expiration;
}
} else {
$expire = time() + $this->sess_expiration;
}
Please let me know if this helps in anyway or modify my code to help me out. Thank you.
$data = array(
'username' => $this->input->post('username'),
'ADMIN_is_logged_in' => true
);
$this->session->sess_expiration = '14400';// expires in 4 hours
$this->session->set_userdata($data);// set session