Some items we should take care of:
- Cookies are easy to steal information
- Attacks via fake request between site or Cross-site request forgery
- Restart the session and delete all cookies after password change, so that everything can be recreated.
- Even if the user remains active, always request the password when involving financial transactions.
- Never store user data in cookies such as email, password, Cpf, number of cards, etc.
- Do not base security only on session ID, as this ID can also be cloned.
- Do not use volatile or transferable IP data to validate a user.
- Never leave the validity of eternal authentication.
- Provide a way for the user to disconnect all places with extended session.
Thinking about the above mentioned criteria, we can arrive at the following model and minimize the risks.
Generating the authentication token
We will need to store authentication tokens on our server, for this a similar table should be created in our database.
- id // Um identificador para o token, não utilize auto_increment,
// pois pode trazer problemas,
- user_id // Relacionamento com as informações do usuário
- token // Armazena o token de autenticação
- browser // Identifica qual browser foi utilizado para autenticar
- last_access // Último acesso (timestamp)
- created_at // Data de criação (timestamp)
To generate the id
from this table, we can use something simple like
md5( uniqid( mt_rand(), true ) );
In this solution we will also store the browser used, to make even more difficult the attempts of attacks, whether by session cloning, CSRF, automated attacks, etc.
In PHP there are functions that make it easy to identify the user’s browser, but if you prefer some alternative form, there are several on the internet. We will not import with browser version to avoid errors due to automatic updates.
To store the last user access, always update the field last_access
using the function time()
.
Out in the field created_at
also use the function time()
.
Token can be generated randomly, for example:
sha1( uniqid( mt_rand() + time(), true ) );
Every time the user authenticates, we will generate a new and store in this table.
In the cookie we will save only the id
of the token and the token
in itself.
$id = md5( uniqid( mt_rand(), true ) );
$token = sha1( uniqid( mt_rand() + time(), true ) );
// Armazenar o token na tabela do banco de dados
$expire = ( time() + ( 30 * 24 * 3600 ) ); // O cookie não deve ser eterno.
$cookieToken = array(
'i' => $id,
't' => $token
);
setcookie( 'auth', json_encode( $cookieToken ), $expire, '/', 'www.dominio.com', isset( $_SERVER["HTTPS"] ), true );
Validating the token
In the previous code we inform that the cookie is only valid for a certain domain, however this can be changed. So we should validate where the request is coming from, and the simplest way to do that is by using the variable $_SERVER['REMOTE_HOST']
, the domain must be known.
After validating the request, retrieve the cookie, deserialize the data and validate with the database.
$tokenData = isset( $_COOKIE['auth'] ) ? json_decode( $_COOKIE['auth'] ) : false;
if( $tokenData !== false ) {
$id = $tokenData['i'];
$token = $tokenData['t'];
// Busque no banco de dados o id e valide o token;
// Veja tambem a validade do token usando como base o campo 'created_at'
// e o browser.
// Se tudo estiver correto, apague este token, gere um novo
// e inicie a sessão.
}
Increasing
To make it even more secure, you can register all Ips and Session_ids that used a particular token.
As every time a new session is started a new token is generated, simply record in a second table the IP that generated the token and the SESSION_ID, together with this data you will record the last interaction with your system and the connection time.
The table will look like this:
- ip // Ip do usuário $_SERVER['REMOTE_ADDRE']
- token_id // Id do token que foi gerado quando o usuário iniciou a sessão.
- session_id // Id da sessão session_id()
- user_agent // Informação completa do browser $_SERVER['HTTP_USER_AGENT']
- time // Tempo de conexão
- created_at // Quando a conexão foi iniciada
- updated_at // Última atualização (função time() para toda atualização)
Every time the user makes a request to our server with the active session, we will update the record of his current connection, retrieving it by session_id
. If nothing is recovered, his session is invalid and we will grant access.
To update we should always check if the IP
and the user_agent
are the same, just as we can check if it has the token that was generated when the authentication was started.
To update connection time, calculate using the field created_at
time() - $created_at
Always check if there is more than one IP using a token at the same time. This may occur when the user’s provider exchanges the IP and the session remains active.
If a token has different IP requests with a very short time between them, we should invalidate the token, and redirect any request linked to it to the login screen.
Remarks
This way has some flaws, but already makes it difficult enough to try to steal session due to the fact that the token is constantly updated.
Database insertion and update requests will greatly increase for this model. To avoid problems, this model should be based on non-relational databases or use a cache layer such as the memcached
, which will receive all requests and from time to time update the database.
We should always alert the user when more than one session with is active, and provide the possibility to invalidate all tokens, and log in again.
We should never allow the exchange of password without having a way to validate the authenticity of who is changing.
It is very important to alert the user about the use of cookies and have a well-written policy about it.
I am confused on two points in your question: 1) you are asking about the "remember me", but your protocol seems to me more a simple means of maintaining the session (which is equal to what 99% of the sites use, by the way). What is your goal with this remind me? Is it simply that the user does not scroll down after closing the browser? 2) "save user and password (...) do not trust much" What do you mean? Surely you have to save this in BD, otherwise how could you authenticate the user? There are ways to login without needing a saved password (certificates, SRP) but the security is +- the same.
– mgibsonbr
P.S. Related question: "What is the best way to make a password login system with PHP"
– mgibsonbr
This, is exactly the "remember me" IE, keep the session after closing the browser. About this save the password, sorry I expressed badly, but is that I searched a lot before asking here, and in all places the staff saved the user and password in cookies...
– KaduAmaral
In fact, even though the password is a shared secret between user and server - so that theoretically It would be okay to move it from there to here - there are good reasons not to do this. Among them is the fact that the cookie data is permanently saved on the user’s computer (ideally the passwords only exist in memory, and there is a lot of effort being made - without success - to achieve this goal). You have every reason not to trust this method!
– mgibsonbr