Login com Lembra de mim no CakePHP 3 Artigo

Conheça os cursos gratuitos do WebDevBr! - Inscreva-se!


Este artigo foi publicado a 7 meses, 4 semanas, 2 dias atrás.

Um recurso básico, mas ao mesmo tempo extremamento delicado e longe de ser simples, a não ser do ponto de visto de um tolo, o famoso checkbox "lembrar de mim" nos formulários de login merece uma atenção dedicada e constante.

Neste artigo quero mostrar como implementar um "lembrar de mim" na autenticação do CakePHP 3.

Não vamos usar a senha do usuário, em vez disso vou usar um token aleatório que vai mudar a cada login de forma que será impossível decifrar sua senha real já que ela não estará sendo usada.

Gostou deste artigo?

Receba atualizações semanais com novos artigos do WebDevBr e outras dicas!

Antes que me perguntem, sim, estou levando em conta que você já tem isso funcionando.

Atualizando o banco

A primeira coisa a fazer é incluir um novo campo no banco chamado remember_token com limite de 64 caracteres, aqui um arquivo de migration.

<?php

use Migrations\AbstractMigration;

class UserRememberToken extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     * @return void
     */
    public function change()
    {
        $this->table('users')
            ->addColumn('remember_token', 'string', [
                'default' => null,
                'limit' => 64,
                'null' => true,
            ])
            ->update();
    }
}

Estou partindo do princípio que você usa uma tabela users para se logar, faça a alteração se necessário.

Atualizando a view.

Na view basta incluir um checkbox com nome remember.

echo $this->checkbox('remember', ['label'=>'Lembrar de mim']);

Ou em HTML:

<input type="checkbox" name="remember" /><label for="checkbox">Lembrar de mim</label>

Atualizando o controller

Nossa lógica será a seguinte, caso o usuário marque a opção "Lembrar de mim" na view vamos incluir um valor no campo "remember_token" do banco de dados e criar um cookie com este token e o id do usuário. Quando o usuário voltar, tempos depois e com a sessão já expirada, um novo login será solicitado, neste momento verificamos o cookie e, caso exista, eu busco o usuário, verifico o token criado e faço a autenticação automaticamente.

Algo como isso:

public function login()
{
    $this->ViewBuilder()->layout('ajax');

    $remember = $this->Cookie->read('remember');

    //verifico o token para fazer login
    if ($remember) {
        $user = $this->Users->find()
            ->where(['id'=>$remember['id']])
            ->first();
        if (!empty($user) and $user->remember_token==$remember['token']) {
            return $this->Remember->authRemember($this->Users, $user);
        }
    }

    if ($this->request->is('post')) {
        $user = $this->Auth->identify();
        if ($user) {

            //verifico se o campo "lembrar de mim" foi marcado para incluir o cookie
            $remember = $this->request->data['remember'] ?? null; //esta linha só funciona no PHP 7
            $user = $this->Users->get($user['id']);
            return $this->Remember->authRemember($this->Users, $user, $remember);
        }
        $this->Flash->error(__('Invalid username or password, try again'));
    }
}

Note que em vez de usar o $this->redirect($this->Auth->redirectUrl()) eu usei $this->Remember->authRemember($this->Users, $user, $remember);, sem o teceiro parâmetro da primeira vez, vamos criar ele, este parâmetro desativa a criação do cookie quando informado false, o valor padrão será true, ou seja, por default ele irá criar o cookie.

Note que o código $remember = $this->request->data['remember'] ?? null; só funciona no PHP 7, atualizar seu PHP é a melhor dica que eu posso dar, existem poucas compatibilidades (arrisco dizer nenhuma?) entre PHP 5.6 e PHP 7 no que diz respeito a upgrade, o downgrade de versão, claro, tem muitas incompatibilidades, o ?? é uma delas.

Component Remember

Aqui nosso componente completo.

<?php

namespace App\Controller\Component;

use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;

class RememberComponent extends Component
{
    public $components = ['Auth', 'Cookie'];

    public function authRemember($model, $user, $setCookie = true)
    {
        $this->Auth->setUser($user->toArray());
        if ($setCookie and $user->remember_token) {
            $this->Cookie->config([
                'expires' => '+30 days'
            ]);
            $token = bin2hex(random_bytes(32));
            $user->remember_token = $token;
            $cookie = [
                'id'=>$user->id,
                'token'=>$user->remember_token
            ];
            $model->save($user);
            $this->Cookie->write('remember', $cookie);
        }
        $controller = $this->_registry->getController();
        return $controller->redirect($this->Auth->redirectUrl());
    }

    public function logoutRemember($model)
    {
        $this->Cookie->delete('remember');

        $user = $model->get($this->Auth->user('id'));

        $user->remember_token = null;

        $model->save($user);

        $controller = $this->_registry->getController();
        return $controller->redirect($this->Auth->logout());
    }
}

Tenho dois métodos, um para login e outro para logout, o de logout remove o cookie. não da pra fazer logout sem remover o cookie, já que o sistema sempre nos loga novamente assim que saimos.

Além disso eu tenho um $setCookie no método authRemember(), com ele eu posso ignorar ou não a criação do cookie e evitar mais um nível de indentação no método login.

Logout

Quer saber como ficaria o método logout do controller?

public function logout()
{
    return $this->Remember->logoutRemember($this->Users);
}

Além de remover o token eu também zero o campo remember_token do banco, assim também não existirá token nenhum para tentar usar no banco.

Conclusão

Queria ter aproveitado para falar um pouco sobre segurança neste artigo, mas iria ficar demasiadamente grande, preferi então uma abordagem prática e direta.

Uma dica adicional é você criar um segundo campo com um valor alatório e não relacionado ao id nem ao username do usuário e usar ele para identificar ao invés de usar o id, melhora sua segurança e você pode alterá-lo a cada novo login, só garanta não ter este valor repetido no banco.

Gostaria de reforçar que é essencial que você analize, discuta e amplie as possibilidades deste código, tornando cada vez mais seguro e completo.


Cursos relacionados


* Parcelamento apenas cartão de crédito! Pode haver uma pequena variação no parcelamento em relação a simulações apresentadas!