As in many web applications, when my users log into my site, I set several session variables that control permissions for accessing site functionality.
Whenever a user undertakes an action that would result in a change to those permissions, I update the session variables as part of the normal processing flow.
But sometimes a user's session needs to be updated based on the actions of another user. For example, a moderator upgrades a user's permissions. The moderator does not have access to the user's session space, so the normal update functions cannot be run for the affected user.
I have considered a few ways of forcing an update to another user's session, but they all have drawbacks:
I could search for and delete their
session from the sessions table, but
this requires a full table scan
(where sessdata like '%user_id%'),
and it could cause the loss of
content that the affected user may be
engaged in generating.
I could force a session update
periodically, such as when
sess_time_to_update is triggered.
But there is no guarantee that this
will transpire prior to the user
attempting to access the
functionality for which the update is
needed.
I could run a complete series of
variable updates on every page load,
but the whole point of maintaining a
session is to avoid that overhead.
I could set a flag indicating the need
for a session update, but that signal
would have to be interrogated at every page execution, and in every controller. (I know that I could extend CI_Controller as MY_Controller, but I don't want to do that)
I'm already sending the user an email
notifying them of the change in
permission, and it's easy enough to
make them click a session update link
(or even log out and log back in).
But there's no guarantee they are
going to even read beyond the subject
line, let alone actually click the
link.
So is there anything I'm missing? Am I looking for a magic bullet that does not exist?
(Note that I've tagged this with CodeIgniter, and made reference to CI-specific technical details, because that's what I'm using. That said, this isn't a CI-specific (or even a PHP-specific) issue.)
Thanks for the help!
Well, one option would be to set an "ACL Version" number (either for all users, or for each user). Then when initializing the session (well, when you call session_start()) check to see if the stored version matches the session's version. If it doesn't, refresh the ACL.
Another slightly different way would be to add a column to the sessions table ("status" or "dirty", etc). Then, instead of killing the session, simply update that status to 1. Then when loading the session, check that flag to see if it's 1. If so, reload the cached data in the session and set it to 0. If not, continue on...
As far as updating another user's session, I wouldn't do that... If sessions are managed by PHP, then you'll need to kill the current session and start the one you want to modify. That's just asking for problems... (you need to do that, since PHP's mechanism does not use serialize.).
As far as deleting their session, I wouldn't worry about the full table scan. Unless you have a TON of users updating permissions all the time, it's not going to matter. The data loss is a significant concern, so that nicks that idea...
As far as the other 2 options, I think you hit the nail on the head, so I have nothing else to offer on them...
I'm going to take a stab, though my approach is not entirely applicable to Code Igniter.
The way I've solved this problem in the past is to make a User object model with a constructor that takes the UserID from the database primary key where the credentials are stored. I'll write a static login method that checks login credentials and then instantiates and returns an instance of user if the login is correct for a row and then sets the session.
So far so good, right? So all your permissions, access levels etc are stored in the database. Just like having a login method, we can have a refresh method that reinstantiates the object, re-fetching from the db off the already obtained primary key.
class User{
public function __construct($uid){
//fetch from the db here
$sql = 'SELECT FROM User_Table WHERE UserID = ?';
$params = array($uid);
//fetch and assign using your flavor of database access, I use PDO
//set all your object properties for access, as well as user_id, something like
//$this->user_id = $result['UserID'];
}
public static function Login($uname, $pass){
$sql = 'SELECT UserID FROM User WHERE Username = ? AND Password = ?';
$params = array($uname, md5($pass));
//again I'm going to employ pseudocode here, fetch according to your preferred system
if(!empty($result)){
$_SESSION['user'] = new User($result['UserID']);
}else{
//login failed!
return false;
}
}
final public function _refresh(){
//refresher method. Since the controller sets all the object properties for access
//reconstructing and assigning it refreshes these priveliges.
$_SESSION['user'] = new User($this->user_id);
}
}
Using this model, whenever I am performing an action with the user in the session that might potentially need time-sensitive permissions, I can call refresh on the user object all ready in the session. Let's say we have a controller function to access a restricted form.
function _topSecret(){
$_SESSION['user']->refresh();
if($_SESSION['user']->specific_permission_from_db){
//Perform the special action, get the view, whatever.
}else{
//redirect to an error page
}
}
So if you already have routines written for the admins, all they need to do is set the user's privileges in the database, and when the refresh method runs on the specific, time-sensitive functions, that data will be put in the session for the user. So you're not necessarily updating another user's session, you're just determining which actions are so chronologically sensitive they require the newest permissions and issuing that command to the user object in the session.
This has proven pretty efficient for me, since reconstructing need only be performed on actions that need the most up to date permissions. Again, I'm not sure how useful it may be in the context of CI and your individual application, but I figured I'd share. I'm assuming you've already done all the necessary session security measures and started the session - most frameworks like CI will handle this for you through their native means.
Add a column in the session table that can hold the primary key of the user object. Set it upon login and use it later to update the session.
Edit: If you don't want to extend the session table, make an additional lookup table where you have the user object id and the session key link.
If your're storing the session as a native php session I'd tend to leave it alone, and allow them to get some kind of notice that they need to login/out to refresh the setting. I've also had sites where the session data is stored in the db and then its much more trivial to just write the new setting in.
Related
I'm developing a site in PHP. When the user session starts I load all his db row in the $_SESSION var. When the user changes a db value I update the $_SESSION var too.
The problem starts when more than one session is active for the same user. Is there a way to update the data for all the sessions of the same user without overloading the database? Or, alternatively, is there a way to force php to use the same session file for all the session that belongs to the same user? Or I must simply query the db every time a session continues?
And another dilemma is: is it worth it? I mean, I do not know how much this mechanism could alleviate the server load, and I do not know if this mechanism is applicable to file-based sessions or I must use another session storing type.
This question is somewhat related to this other question on mine (even if the workaround for this is simply to delete all session files).
It really reaches the question why would you need to many data in a $_SESSION. And you should really take a time to decide which data is so often needed to be displayed.
In most of the cases you only need session identifier that keeps the user logged in, containing user_id, to take the needed data directly from the database.
Assuming the user can change its avatar, and you haven't go so many places to display this avatar, you don't need to store it in session, nor to SELECT it at the very same time. For instance, you can have a trigger page, which SELECTS the avatar by $_SESSION['user_id'] when he tries to send personal message to another user. Otherwise, you can put a cache (i.e. using memcached) where a query, which selects the user avatars should not be made more often than once an hour.
If user changes an email, it's the same. If somebody else tries to send him message, you trigger the SELECT query. Otherwise a cache is set.
So, let's say the user has changed his avatar, email, some other trivial info, then accessed your index page. In his session you load only the identifier. In the db the records are present, but they are not selected yet. So you have neither server load, because the session is light, nor database load, because no SELECT queries were sent.
No matter how many times the user tries to set his session (in this case logs second time), you have a present data in the db, and a session only with identifier. You can identify all his instances, but never use a data, which is not needed.
1 Well, I (don't, but) could do this with my session handler. I use databased SESSIONS with some extra information/columns like username and userid. That way I can exactly determine which session belongs to which user without fiddeling around with the serialized data.
http://php.net/manual/de/function.session-set-save-handler.php
2 But in your case it might be simpler to update your user table and then SELECT the user again to put the (new) data to $_SESSION['user']. (You will need some "user data was updated" info, to reload new data for all sessions).
3 Or you just avoid that a user can login more than once.
I've developed many login systems in PHP. Basically, for each website or application I created, it had a login scheme to create articles, upload images, edit comments e blablabla.
I've never had problems with that, except once when I created a kind of social page inside my website. There was a user bothering the other users, so I decided to delete his profile, that's why I'm here asking your help.
At the time, I was just checking the session on each page, for example:
<?php
if($_SESSION['loggedin'] === true)
{
// Keep that page
}
else
{
// redirect to login page
}
?>
Then, when I deleted his profile the session wasn't closed yet, after that the user continued annoying the other users, and I wasn't able to do anything.
So, what's the most common and best way to handle sessions on each page: Check the database each time or just check if the session is true?
I don't know whats the best way, but I do something like this:
I have an sql table with the sessions (for example userid, sessionid, expiredate, ...).
The sessionid is "saved" in a $_SESSION['cms_session'] .
If the sessionid which is in $_SESSION['cms_session'] doesn't exist in the session table, the user isn't loged in anymore.
For deleting the old sessions in the table i use crons.
What you are trying to do is have a single place where you can maintain user status and know that a change will be reflected immediately.
Checking a "user_status" field in the DB is a pretty efficient call to make on each request. This provides a single place where you know that if you deactivate a user, the changes will be reflected upon their next request. You can also do this easily without writing another set of routines to look through session variables or to create some sort of messaging system where the application announces that a user has been deactivated.
Checking the database each time a page loads is really inefficient. If all you're trying to do is kill his session, you should store sessions in memcached where the 'key' is based on the username, something like "johnsmith-session" and then on an admin page, send a message to memcached to kill that key, which should immediately log him out of your site.
If PHP is currently writing session data to disk, depending on how the data is serialized, you may be able to track down his session file on disk and delete that file, which will accomplish the same thing: the next time that user tries to load a new page, his session will be invalid and he'll be required to log in again.
Keep in mind that really persistent trouble users will often re-register a new account to continue their antics, so you'll want other means of watching for new registrations from that person.
I am looking for insights into how to destroy a specific session in PHP. Through a partner website a user logs into the main website using a token and obtains a full session.
It is also possible for the partner website to call a destroy function if the user logouts from the partner website. We should then also log out our own user.
What is the best approach to this? The Zend_Session destroy method does not accept a parameter, similarly the PHP function session_destroy does neither.
I am considering two options:
Removing the session information directly from file/memcache but would prefer a "cleaner" approach than this.
Checking at every page request if this is a "token" user ; and if then check if their token was expired by maintaining a list. This adds overhead to a busy website, but might be my only option.
Or is there a third / better approach I am not seeing?
There's no need to roll-your-own session handling.
session_id() can take a parameter, the session id you want to work with.
So, when you pass the user off to the partner site, pass along their session_id (or some token, or whatever).
Then allow the partner site to hit a script like this:
kill-user-session.php
<?php
/**
* Destroy any active session identified by $_POST['sid']
*/
session_id($_POST['sid']);
session_start(); //this line may not even be necessary
session_destroy(); //destroys that session.
So when the user logs out on the partner site, the partner site POSTs the session_id (that you gave them) to your kill-user-session script, and the user's session is destroyed on your server.
Of course, you probably want to limit access to kill-user-session.php via some method or another.
If you wish to be able to 'kick' the sessions of a user(s), the only way you can do it is if you use MySQL (or someother db, sqlite even) for your session storage.
Then you can simply remove entries from the db to kill a session.
This also allows you do do things such as, 'take control' of a specific user's session and other stuff :)
See this for a very basic run through: http://www.devshed.com/c/a/MySQL/Custom-Session-Management-Using-PHP-and-MySQL/ (not the best example but good enough full example to start you).
EDIT
Also, if logging out through the partner site, another method I have used in the past (which was with O2 and other such sites) they were given a 'callback' (REST API call in most cases) which they would also need to call when the user logs out of their site.
The database solution means that the session database needs to be shared between mainwebsite and the partner site, which frequently isn't the case etc. Maybe something along these trivial lines would suffice?
<img src="mainwebsite/logout.php">
mainwebsite/logout.php:
<?php session_destroy(); ?>
In my PHP Web-App I use sessions to store the user's data. For exmaple, if a user logs in, then an instance of the User class is generated and stored in a Session.
I have access levels associated with each user to determine their privileges.
Store the user in a session by:
$_SESSION['currentUser'] = new User($_POST['username']);
For example:
if($_SESSION['currentUser'] -> getAccessLevel() == 1)
{
//allow administration functions
}
where getAccessLevel() is simply a get method in the User class that returns the _accesslevel member variable.
Is this secure? Or can the client somehow modify their access level through session manipulation of some sort?
No, the client cannot modify their access level. The only thing stored on the client is the session key which is either propagated via cookie or GET parameter. The session key ties to a corresponding session record which is a file stored on the server side (usually in a temp directory) which contains the 'punch'.
What you don't want, is for a session key to get leaked to a third party:
A leaked session id enables the third
party to access all resources which
are associated with a specific id.
Take a look at this: http://www.php.net/manual/en/session.security.php
The session information is stored on the server and the user only has access to a key. In practice I have used something of this sort, with extra steps. After validating the user details and storing the User object, I would have a query that is run when viewing any of your protected pages to validate what is in the session is okay with what they're trying to view.
In the top of your page.php
if(!validUser($user)){
// Relocate the user
}
where
validUser(User $user)
{
// Some query to verify the information in the session
// Return the results of verification
}
I thought the only way for the user to manipulate something like that was if it was stored in a cookie on the users computer.
Is the getaccesslevel stored to a cookie or is it called from the server only after checking the login cookie and not stored on the users computer?
I would assume that if it is called on the server only after the user is logged in then they would not be able to easily manipulate that other than through other means of security holes.
Just my guess tho, im not that great with security myself yet. I will keep an eye on this to see what others have to say and maybe I can learn something.
I'm working a site where users could technically stay logged in forever, as long as they never close their browser (and therefore never get a new session key). Here's what I could see happening: a user leaves a browser open on computer A. The then use computer B, login and change their name which is stored in the session. They logout of B, but A is still logged in and still has their old name stored in the session. Therefore, their name won't be updated till the next time they logout manually or they close their browser and open it again and are logged in through the remember me function.
Name is a simple example, but in my case the subscription level of their account is stored in the session and can be changed.
How do you deal with this?
A few ideas that I have are:
After a period of 10 minutes or more, the session data get's reloaded. It might be exactly 10 minutes if the user is highly active as the function will get triggered right at the 10 minute point or it could be after 2 hours if the user leaves and comes back and then triggers the functionality.
Store as little information as possible in the session and load the rest from the DB on every page call. (I really don't like this idea.)
Use database sessions and use the same session on all the computers. I like this, but I could see it getting confusing when something like search criteria are stored in the session--the same criteria would show up on both browsers/comptuers.
For information, even such as the user's name or username/email address, store it in the session, but for other information that would heavily affect their abilities on the site, don't store it in the session and load when needed (attempt to only do it once per instance).
Are there other better methods?
--
Another option: 5. Use database session and when an update is made load the user's other sessions (just unserialize), change the relevant information and save them back to the database.
I would go either with number 1 or number 4. If you store the time of the last update of the information, you could even ask on every request whether the date has been updated.
Don't store information likely to change in the session, if you're looking at scenarios like the one you outline. Just get over your dislike of loading user data with every page - it's by far the best idea.
I'm guessing you don't want to load the data from the database because you're concerned about performance issues somehow. Before you try out any of the other solutions, you might want to test how long it takes to actually load a users data from the database, then check that against your number of users - chances are you won't see any performance problems due to loading user profiles on every page.
Regards
I'd go with option 6: only store userid and session specific stuff (search criteria) in his session and put the rest into APC/xcache (memcached if you're using multiple servers).
this way you'll only have to go to the database the first time (and after the cache expires) and you can still share any data between users sessions.
Normally you should do 2), but you don't like it.
maybe you can use sessions stored in db.
when a user change his name, put into all sessions from that user the information "refresh userdata".
on the next request the userdata is reloaded again into the session and is cached there.
this can be done be reusing your loaduserdata function which called at login.
php session_set_save_handler() - also read comments
php session_decode() - to read the username from the session to store it additionally to the sessiondata. usefull for easily to find the users sessions for updating.
[edit]
don't forget:
when you are updating all the sessions while the page is generated (between session_start and session_write_close) you changes maybe lost.