Preventing Numerous Devices Concurrently signed into a Single User Account - php

I have a functioning authentication, ACL and session management system for my online software application. As this is a paid service on a per-user account basis, I need to ensure that a user account cannot be shared between employees in the same fashion that PayPal manages business accounts.
My struggle is finding a way to do achieve such functionality that a competent, technical user couldn't circumvent via modifying Javascript or using a client side proxy.
Currently, each company account has a database on my server with a "session" table that consists of session_id (PK), session_user, session_data, client_ip, timestamp_created and timestamp_updated columns. Whenever a user logs in, the session_user column is updated with their login name. Should another person log into that account on a different machine, the previously accessed account will have the session_user column set to NULL. The result is that no two rows should have the same value for session_user. Possibly worth noting is that customers do not have administrative access to their database.
The client-side application runs a Javascript function on a timer every 5 seconds which performs an AJAX call to a Zend Controller. This controller checks the session_user column for the active session via a DB query using Zend_Session::getId();. This method echo's a JSON encoded response with success set to TRUE if the session_id has an associated session_user in the database, or FALSE if there is no value in the session_user column. In the event that the method returns FALSE, the Javascript function proceeds to alert the user that another machine has logged into their account, and then redirects them to the login page.
The problem with this approach is that anyone with experience in Chrome's inspector or Mozilla's FireBug can remove the Javascript that performs the AJAX call and proceed to use the application on numerous machines. Worse yet, this easy method is only the beginning of vulnerabilities.
I have a feeling that I will need to look into Zend's plugin architecture, perhaps implementing a check during preDispatch() to ensure that the current session_id has value set in the corresponding session_user table. This check would be complimentary to my current structure, as I feel that the majority of my users will not be well versed in basic "hacking" per se, and will do just find to prevent account sharing. If this server-side check fails, as it occurs when a resource is requested, an error could be thrown and the requested data would not be pushed to the front end (e.g. the data for viewing products).
Does this approach seem like it will work, or is there a better way of achieving such functionality? I've read a bunch of SO posts that derail into arguments regarding whether or not it is a good idea to be so cumbersome on the end-user. We have already decided that since revenue is largely based around company accounts purchasing additional user accounts that there is a significant need to prevent any sort of account sharing. Preventing the user from logging into an account with an existing session is something I want to avoid. Believe it or not, I would rather have users be annoyed by being booted out of their active session by another device logging in, as I feel this will encourage additional user licenses to be purchased.
Please feel free to ask if you would like any additional information, or require clarification about anything I described in my post.

Store the user's IP every time an action is done on your website as a database entry using $_SERVER['REMOTE_ADDR'].
Check if the IP has changed alongside the session data from the previous entry - if so, have them log out and log back in.
This way, separate devices cannot concurrently act without having to relog as a result.

Related

How to avoid that a user removes his session

The use case
Currently, I am trying to build a page where users can vote on content (up/downvote, similar to the function on the StackExchange network). But the users shouldn't need to register themselves to vote on content. So it would be a kind of "anonymous" voting page. It is built with Laravel5 and uses a MySQL database to store the votes. The user sessions are stored in flat-files, but can be also stored in a database table (L5 is quite flexible here).
The problem
How to make it secure?.
I am storing restrictions and already voted contents in the user sessions, e.g. when the user has voted on content XYZ (so the user cannot vote again on the specific content for now). Those restrictions are time-based, mostly 24 to 48h. This works well, as long as the user does not throw away/delete his cookies, which would cause to create a new session and remove the time restrictions, which could lead to easy vote fraud.
So, how to avoid that the user "loses" his session? The focus is on how to let the restrictions and limitations of each "anonymous" user persist! Shared PCs or voting on different locations cannot be avoided when voting anonymous, but "botting" or a vote fraud in large numbers needs to be avoided with a given solution.
Solution attempts
Setting the sessionId of each users session to a combination of IP and
User-Agent
I've asked a question about this attempt (linked below), but it'd open up more problems then it'd solve (e.g. easy session spoofing). Also, I couldn't achieve to set the sessionID manually by using Laravel5.
Solutions that doesn't fit
Let every user register themself (it's simply too much effort for each user in my use case)
Related
How to remember an anonymous vote
Retrieve or reassign user session from ip and user-agent
as users are not stored and maintained its very difficult and can't be made 100% sure.
how i try to achieve this most closely is using request ip address and csrf token.
you can get ip address from request and csrf_token() from anywhere inside your laravel application.
here is an example of how i am going to implement
create a table named votes having following fields
votable_type
votable_id
ip_address
csrf_token
i would check whether a client does not have an existing record for same votable type and id. client is a the csrf_token. ip is for guaranteeing whether the requests are legit.
votable type and id is the polymorphic relationship between either may be comments, posts etc.
note
without persisting user identification in anyway some users might not be either vote or some might vote twice. it can't be done
perfectly.
some users might vote from different user agents multiple
times.
some users might spoof ip. clear cookies
different users might be using same
system to login.
some users might be using different connections or
system logins.
so either we take any information it wouldn't be 100%
accurate.
My solution was combination of implementing evercookie to assign a "Identification Cookie" per user, detecting privacy browsing and restrict access when having Incognito mode or private browsing enabled, and finally restrict several actions (voting in my case) when not having the evercookie.

Security vulnerability from saving user info in PHP sessions?

I am creating a RESTful API-centric web application. Once a user logs in they will receive a session id and login key that will be used for accessing their data until their session expires. The web application (and possibly the mobile applications) will call an API every page load to get user information if a session is saved in the memory. I am working on optimizing this API call as much as I can, and I wonder if it makes sense to cache this information.
Every table with user data contains a updated timestamp (triggered on every Postgres update). So I could modify the API to accept an optional cache_timestamp parameter. The API would first check to see if any of the user data's tables have been modified since that timestamp. If they have, then it would return the updated user data; if not, it would return a 304 not modified and the application would use the cache.
My question is what information is too sensitive to save in memory (using PHP sessions). Currently the information contains things like profile (name, company, etc), contact (email, phone), settings (newsletter, notifications), and payment info (plan, trial, and an customer ID that refers to Stripe).
The only thing that I think would be on the edge is payment info, but they shouldn't be able to access any data from Stripe unless my API keys are compromised.
I'm not a Security expert, but since the sessions are stored on the server, the only way for an attacker to access the data is to have gained some privileges already.
You can look at this interesting post PHP Session Fixation / Hijacking about how to secure more your sessions.
If the user's session got hijacked, then there is nothing to do, the attacker will access the data like if it was the user in question.
If the attacker can exploit a fail related to your server, then he should probably be able to read the session data (which is stored serialized in some files by default).
So, crypting the sensitive data, can prevent him from reading it raw.
My opinion is also same, Payment info should be store in the Session variable, but then you are saying it's not accessible without API key. I think you are at safe side.

Ensure web app access from a single computer per user

I have developed a web application in PHP for a client. The client is now renting out access to the system to another company on a per user basis.
Is there a way to prevent the secondary company to use a single login and give it to 20 people to use at the same time? I know one can get the IP address of the client machine that is being logged in from, but this is obviously not very reliable method. The answer probably lies in a combination of cookies and tracking things in a database, but my brain gets a bit stuck thinking on how to implement a strategy here.
Create a unique session ID when a user logs in and store that in the DB. Add something to the session authentication code (run on all page visits) that checks that the user's session ID is equal to the one in the DB and if not, log them out. Then your web app will be accessible by only one user at a time.
To be completely honest though, can't you raise this issue with your client?
No way to tell if the login is shared among 20 people. You can restrict access by blocking simultaneous usage thru session cookies.
Most of all, protect yourself with a published Terms and Conditions document. Violation of which - revokes any standing agreement/contract. And sue them if you can provide evidence (logs) that they violated it.
Make sure you bind one user to one session. In that way you can generate a warning screen if somebody uses the same login with another session. You can then let the user choose to close the other session.
In that way you can make sure two users are not using the system at the same time. It's a bit like a software program you have installed on a computer: multiple users can use it, but only one at a time. This is probably fine.
If you don't want that, you should try to bind the login more firmly to the user: make sure he logs in with a personal e-mail address, and he gets notifications (if applicable) via e-mail. Also let the user set personal configurations. In that way you create extra value for users to have their own account.
If you have a login you have authentication, and you write any user id in session, make sure that only one session with this id created, if the session already exists throw error message.
The only problem you will have in case and user did not logout properly, instead of it pressing x button on browser then he will not be able to login till session s not expired.

Letting users try your web app before sign-up: sessions or temp db?

I've seen a few instances now where web applications are letting try them out without you having to sign-up (though to save you need to of course).
example: try at http://minutedock.com/
I'm wondering about doing this for my own web app and the fundamental question is whether to store their info into sessions or into a temp user table?
The temp user table would allow logging and potentially be less of a hit on the server, correct?
Is there a best practice here?
It should work exactly the same way the application usually works, with the only difference being that a flag like thisIsATrialUser is set. You shouldn't create two different ways to do things internally.
Create a class of user, lets call it your Anonymous User Type. Give all unauthenticated users anonymous accounts (you have to clean up old accounts at some point). Use a persistent cookie to associate old users with their anonymous account. Make them authenticate themselves whenever they need to perform something that requires payment or full registration. Change their user type to something like Regular User Type once they are authenticated so you can keep all the information that was already attached to them when they where anonymous.
This allows tracking and storing of potential information like shopping carts without requiring registration upfront. Your code shouldn't have to change much if you treat anonymous user similarly to regular users. Otherwise you have to create an entirely new set of code to manage special users that are not stored in your master user table.
To clean up the data added by trial users, you can create a script to delete all the data that was created lifetime of cookie + 1 day and owned by any trial user. You can auto-pilot the script with nightly cron.

Login System design to allow each user to be logged in on one machine at a time

How should I design a login system so that each username can only be logged on in one place at a time? I want to keep users from giving their username to someone else to login so they can avoid paying for each user.
If a user is already logged in and tries to log in on another machine should I block the 2nd login (which could be a problem if the user was logged on at work and then tried to get on at home)? Or should I allow the 2nd login and end the 1st login? Or does anyone have a better suggestion?
Some Instant Messengers (that can work only with one logged in endpoint) have a nice way of sorting out such conflicts. They show a message like
You are already logged on from <COMPUTERNAME>
(in case of a web app, that would be <IP/Browser>)
and give you a choice between
either leaving that logon alive (and not log on from the machine you're on), or
ending the existing logon (and logging on on the current machine).
This is technically the most challenging, but definitely the most friendly way - it ensures a user has only one session running, without being too obvious about it. And there is no bad blood with users unable to log in because they forgot to log out at work, etc.
Blizzard's World of Warcraft I believe implements this beautifully.
Basically, if you try to sign into the game after already being signed in, the first connection is kicked off.
This basically just entails making the session stored on the database. When you store the session data, store a username too. When a user logs in, delete any session records with that users name, and then create a new one for the person logging in.
I wouldn't suggest blocking 'new' people trying to log in, because users don't want to have to go back to another computer they have (possibly miles away) just because they forgot to log out.
There are also some other things you might have to think of. Things like sessionid hijacking. If a user just puts a cookie on their system (which is always possible) with the right sessionid, it is possible that they could use the same session on multiple computers. In which case you'd probably want to keep an IP field where you keep the data on who is currently logged on.
A typical approach to this problem is to use an
inactivity time-out period.
This system enforces a maximum number of logins per account, while allowing for the situation mentioned: a user left the office without logging out, and attempts to login from his/her home workstation.
Here are the general lines of such a system
Each account is associated with a number of concurrent logins (aka "seats") allowed (it seems the OP wished one and only one, for every account, but this could be more, and vary on an account basis).
The license manager logic keeps a list of all accounts/users currently logged-in, along with a time stamp with their "last" activity.
Before serving any page, the web application, calls the license manager (LM). The purpose is to allow the LM to update the timestamp of "last" activity, but also to deny the call in case the license was taken (more on this below)
Upon each login, the license manager logic verifies that the number of seats taken doesn't exceed the amount specified for the account.
If that is not the case, the LM simply adds the current session to the list of active session
If that is the case, the LM check for sessions in the list which are older than the time-out period. If one is found, it disables it, and grants access to the new login. If none is found, the login is denied.
upon each [explicit] log-out, the LM removes the corresponding session from the lists of active session.
Note that the general principle outlined above can have some variations, in particular:
rather than silently and systematically invalidating the [typically oldest] timed-out session, one can inform the user currently attempting to logging about this situation and let him/her decide of the need to "kill" such a session.
To avoid burdening the LM with each and every new page request, the web application can keep track on a per-session basis of the time since the session was last "refreshed" in the LM, and only call the LM if such time exceed say 1/3 of the time-out period.
Independently from the LM logic per-se, remember to keep a log of all the LM-related events (logins, logouts, inactive session "kills", refused logins...). Such logs should include the date/time, the IP address and other relevant info, and are useful when resolving issues associated with stolen passwords and such. Such logs also contain invaluable marketing, for example to find all accounts which appear to have too few seats (and could therefore purchase some ugrade), or to find at-risk accounts etc.
A few more considerations
make it easy for users to log-out (log-out button/link on most every page, at a fixed location
make it easy for users to report conflict / stolen password situation
Block the first login. If you log in at home, then in work, you don't want to be blocked, since this is a legit method. Always allow the login in the present, and drop the old ones.
I would suggest keeping track of whether each user is logged in and allowing the second login to end the first login's session.
Then allow the user whose session has ended to report possible fraudulent activity if they were kicked off in error.
Don't try to do it by counting the number of IP addresses a user has an active session from - some users may be behind load balanced proxies.
The solution is to write your own session handler - probably easiest with a database back end - and only allow one user to have one open session.
You might want to tune the session garbage collection and inactivity. You should also ensure that your system is immune from session fixation attacks.
C.
In terms of security, and this is what you're getting at, it is always a good idea to store session data in a database anyhow. Particularly if you're on a shared server.
In terms of which user to allow and which to knock off that is a matter for you to judge. I suppose you could have some secondary form of identification to make sure they are the real owner of the account. The one who actually signed up to it.
I've done this before in a web application that had the same requirement. Here's what I did:
When someone logs in, you generate a GUID and store it in your database, attached to the user. You also store this same GUID in a session cookie.
Every time a logged in user hits any page on your site, you check their cookie GUID and compare it with the GUID that is assigned to them in your database. If these GUIDs don't match, they've logged in on another machine, and you log them out from that session.
This method works really well.

Categories