Slim Framework - Autenticação Artigo

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


Este artigo foi publicado a 2 anos, 5 meses, 1 semana, 3 dias atrás.

Um micro framework muito bom que tenho usado cada vez mais é o Slim Framework, a documentação é clara,  é muito simples integrá-lo com qualquer componente PHP e eu adoro o mode (modos de uso), que são configurações específicas, por exemplo, para produção e desenvolvimento.

Neste primeiro artigo que escrevo sobre o Slim quero mostrar um pouco sobre autenticação,  e claro, temos que configurar a camada de model, então vamos lá.

Instalando o Slim Framework e o Doctrine

Pra começar, vamos instalar e configurar o Slim, o Doctrine e criar um registro de usuário, na verdade é tudo muito simples, vamos ver.

Gostou deste artigo?

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

Você vai precisar do Composer, então: http://getcomposer.org/

Primeiro vamos configurar nosso composer.json:

{
    "require": {
        "slim/slim": "2.*",
        "webdevbr/doctrine": "*"
    },
    "autoload": {
        "psr-4": {
            "App\\" : "src/App"
        }
    }
}

Eu inclui o Slim e o meu pacote de configuração do Doctrine. Também registrei um autoload para nossas classes no namespace App.

De um install no composer agora, use o comando mais de acordo com seu caso:

php composer.phar install //para composer.phar local
composer install //para composer global

Camada de model com Doctrine

Crie um arquivo chamado bootstrap.php na raiz do projeto.

O arquivo bootstrap.php vai corregar a aplicação e conectar no banco de dados, não vou detalhar muito o processo, apenas saiba que você tem que configurar seu acesso ao banco na variável $conn.

require 'vendor/autoload.php';

date_default_timezone_set('America/Sao_paulo');

$isDevMode = true;

$conn = array(
    'driver' => 'pdo_mysql',
    'host'=>'dev.local',
    'user'=>'root',
    'password'=>'123',
    'dbname'=>'slim_artigo_blog'
);

$doctrine = new WebDevBr\Doctrine\Doctrine($conn, $isDevMode);
$doctrine->setEntitiesDir(__DIR__.'/src/App/Entities/');
$entityManager = $doctrine->getEntityManager();

Pronto, vamos criar uma entidade em src/App/Entities/User.php, com este conteúdo:

namespace App\Entities;

use App\Auth\Bcrypt;

/**
 * @Entity @Table(name="users")
 **/
class User
{
    /** @Id @Column(type="integer") @GeneratedValue **/
    private $id;

    /** @Column(type="string", length=100) **/
    private $username;

    /** @Column(type="string", length=100) **/
    private $password;

    public function __construct(Array $data = [])
    {
        if (!empty($data['username']))
           $this->username = $data['username'];

        if (!empty($data['password'])) {
            $bcrypt = new Bcrypt();
            $this->password = $bcrypt->setHash($data['password']);
        }
    }

    public function getId()
    {
       return $this->id;
    }

    public function getUsername()
    {
       return $this->username;
    }

    public function getPassword()
    {
       return $this->password;
    }

    public function __toArray()
    {
        $data = [];
        foreach ($this as $k=>$v)
        $data[$k] = $v;

        return $data;
    }
}

E por fim o arquivo cli-config.php na raiz do projeto, ele vai passar nossas configurações pro console do Doctrine, é bem simples:

<?php

require_once 'bootstrap.php';

use Doctrine\ORM\Tools\Console\ConsoleRunner;

return ConsoleRunner::createHelperSet($entityManager);

Neste ponto basta rodar este comando para cria a tabela no banco:

vendor/bin/doctrine orm:schema-tool:create

Pronto, alternativamente criei um arquivo de model com alguns métodos para limpar um pouco a camada de controller

<?php

namespace App\Model;

use App\Entities\User as Entity;
use Doctrine\ORM\EntityManager;

class User
{
    private $em;

    public function setEntityManager(EntityManager $em)
    {
        $this->em = $em;
    }

    public function create(array $data)
    {
        $user = new Entity($data);
        $this->em->persist($user);
        return $this->em->flush();
    }

    public function findByUsername($username, $array = true)
    {
        $repo = $this->em->getRepository('App\Entities\User');
        $user = $repo->findOneBy(['username'=>$username]); 

        if ($array and $user)
            $user = $user->__toArray();

        return $user;
    }
}

Temos dois métodos, o create() que recebe um array e salva estes dados no banco, e o findByUsername() que recebe dois parametros, o primeiro é um usuario a ser buscado no banco, e o segundo define se vamos receber um array ou um objeto (a entidade que criamos).

Se você quer saber mais sobre o Doctrine de uma olhada no curso grátis ou aqui e na documentação.

Classes de autenticação

Se você analizou a entidade User que criei acima viu que ela usa uma classe chamada Bcrypt, esta classe serve só pra criar um hash de senha (assim se eu precisar alterar depois fica fácil) , ela fica em src/App/Auth/Bcrypt.php

<?php

namespace App\Auth;

class Bcrypt
{

    private $cost;

    public function __construct($cost = 12)
    {
        $this->cost = $cost;
    }

    public function setHash($password)
    {
        return password_hash($password, PASSWORD_DEFAULT, ['cost'=>$this->cost]);
    }

}

O método __contruct() pode ou não receber uma variável $cost, que define o processamento para gerar o hash, se você não passar este valor ele será 12, e o setHash() pega uma string e retorna o hash bcrypt dela.

Muitos se perdem ao definir o custo de um hash bcrypt, o ideal é que esse custo faça o servidor demorar 1 ou 2 segundos para gerar este hash.

Para usar a classe Bcrypt:

$bcrypt = new App\Auth\Bcrypt();
$bcrypt->setHash('123');

Você pode buscar outra forma de usar a classe Bcrypt, da forma que está atualmente nossa entidade ficou extremamente dependente do objeto Bcrypt, isso é ruim, fica como exercício pra você (pesquise por dependency inversion).

A segunda classe vai realmente fazer o login e fica em src/App/Auth/Login.php

<?php

namespace App\Auth;

class Login
{

    private $user;
    private $data;

    public function __construct($data)
    {
        $this->data = $data;
    }

    public function check($user)
    {
        if (!isset($user['password']) or !isset($this->data['password']))
            return false;

        if (password_verify($user['password'], $this->data['password']))
            return true;

        return false;
    }

    public function access()
    {
        $_SESSION['user'] = $this->data;
    }

}

Ela recebe um array $data (ou um valor null) no métodos __construct() com os dados do usuário registrado no banco de dados (devidamente filtrado pelo username), o método check() recebe os dados enviados pelo formulário e compara a senha do banco (a variável $data), e o método access() registra uma sessão com os dados do usuário.

Para usar a classe:

$auth = new App\Auth\Login($user);

if ($auth->check($post))
    $auth->access();

Pronto, agora terminamos nossas classes (o motor da aplicação), vamos partir pro Slim?

Finalmente, Slim framework

Na raiz do projeto crie um arquivo chamado app.php com o seguinte conteúdo:

<?php

use Slim\Slim;
use Slim\Middleware\SessionCookie;
use App\Auth\Auth;

$user = new App\Model\User();
$user->setEntityManager($entityManager);

$app = new Slim();

$auth = function () use($app) {
    if (!isset($_SESSION['user']) or !is_array($_SESSION['user']))
        $app->redirect('/login');
};

$app->add(new SessionCookie([
    'secret' => 'SEU_TOKEN_AQUI',
    'cipher' => MCRYPT_RIJNDAEL_256,
    'cipher_mode' => MCRYPT_MODE_CBC
]));

//aqui vão as rotas

$app->run();

Note que criei um método anônimo chamado $auth, ele é nosso middleware, um middleware é algo que intercepta a requisição e toma uma decisão baseada em uma lógica pré-definida, neste caso, antes de executar o código da nossa rota ele vai verificar se 'user' existe na sessão, se não existir ele vai redirecionar para o login, vamos usar nosso middleware daqui a pouco.

Também adicionei outro middleware, só que desta vez ele configura a sessão, o SessionCookie é nativo do Slim Framework, não esqueça de incluir seu token de segurança (aleatório).

Agora vamos usar o middleware $auth, crie uma rota (aonde está o comentário '//aqui vão as rotas' no exemplo anterior) com este código:

$app->get('/protected', $auth, function () {
 echo 'Página protegida';
});

Simples não, apenas informei o middleware alí no segundo parametro, vamos ver todas as rotas desta aplicação?

$app->map('/register', function () use($app, $user) {
    if ($app->request->isPost()) {
        $user->create($app->request->post());
        $app->redirect('/users/register');
    }

    $app->render('form.php');
})->via('GET', 'POST');

$app->map('/login', function () use($app, $user) {
    if ($app->request->isPost()) {
        $post = $app->request->post();
        $user = $user->findByUsername($post['username']);

        $auth = new App\Auth\Login($user);

        if ($auth->check($post))
            $auth->access();

        $app->redirect('/protected');
    }
    $app->render('form.php');
})->via('GET', 'POST');

$app->get('/protected', $auth, function () {
    echo 'Página protegida';
});

Temos 3 rotas, 'register' para registrar um usuário novo, 'login' para acessarmos o sistema e 'protected' que seria nosso conteúdo protegido por senha.

Pra terminar crie um arquivo dentro em public/index.php com este conteudo:

<?php

require '../bootstrap.php';
require '../app.php';

E outro em public/templates/form.php com este conteúdo:

<form action="" method="POST">
    <input type="text" name="username"><br>
    <input type="password" name="password"><br>
    <input type="submit">
</form>

E dentro de public rode seu servidor (php -S localhost:8080, por exemplo) e acesse as rotas que estabelecemos acima para registrar um usuário ('/register' ou 'index.php/register'), se logar no sistema('/login' ou 'index.php/login') e acessar a página protegida('/protected' ou 'index.php/protected').

Conclusão

Para quem (assim como eu) gosta de escrever seus próprios códigos ou usar componentes específicos (como o Doctrine) um micro framework pode ser a solução, é mais fácil testar e manter uma estrutura minimalista que cresce a medida que você precisa.


Cursos relacionados


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