[PHP] Usando el modulo de autenticación Kohana A1

Kohana A1, es un modulo de kohana el cual nos permite implementar de una forma efectiva, potente y clara, un sistema de autenticación, de una forma fácil y sencilla.

Antes de empezar a usarlo es importante considerar que el modulo usa la el constructor crypt de PHP pero usando como hash CRYPT_BLOWFISH, esto no afecta si estas usando PHP 5.3, pero si tienes este inconveniente al final del post pondré como solucionarlo(o cambiarte de hosting a uno mas actualizado).

Vamos a ver como usarlo para hacer un login.

Requisitos:

  • Kohana 3.2
  • Kohana A1 module
  • Kohana ORM
  • Kohana Database

Como dicen los requisitos para usar A1 debemos de estar conectados a una base de datos, también debemos de tener un modulo que abstraiga dicha base de datos, para esto podemos usar Kohana ORM, Automodeler, Jelly, Sprig o entre otros, si estamos haciendo pruebas yo recomiendo usar Kohana ORM, y ya en base a sus requerimientos y necesidades usar el que mas les agrade.

Instalamos kohana de modo que quede en http://localhost/kohana1/ ,creamos una base de datos yo la llamare, “system” y le pondré como datos de conexión ultra-seguros user:root password:root, recuerden activar el modulo database y ORM.

Y posteriormente nos toca, agregar el siguiente código SQL a nuestra base de datos, obviamente lo pueden modificar a su gusto, aunque de momento dejémoslo así.

 CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`email` varchar(127) NOT NULL,
`username` varchar(32) NOT NULL DEFAULT '',
`password` char(80) NOT NULL,
          `token` char(80) NOT NULL DEFAULT '',
`logins` int(10) UNSIGNED NOT NULL DEFAULT '0',
`last_login` int(10) UNSIGNED,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
view raw users.sql This Gist brought to you by GitHub.

Ahora procedemos a bajar el modulo A1, que esta aquí: https://github.com/Wouterrr/A1/

Y lo ponemos en nuestro carpeta modules nombrando a la carpeta A1 de modo que quedara asi “modules/A1″.Dentro de dicha “modules/A1″ hay una carpeta config y dentro de ella un archivo llamado a1.php, bien este archivo es el archivo de configuración del modulo Kohana A1, de igual forma que se hace con todos los módulos hay que copiar este archivo a “application/config/” para poder editarlo y que no afecte directamente al modulo.

El archivo se ve de la siguiente forma:

<?php

return array(

'driver' => 'ORM', // orm/jelly/mango/sprig
'user_model' => 'user',
'cost' => 12, // Bcrypt Cost - any number between 4 and 31 -> higher = stronger hash

'cookie' => array(
'key' => 'a1_{name}_autologin',
'lifetime' => 1209600, // two weeks
),

'columns' => array(
'username' => 'username',
'password' => 'password',
'token' => 'token',
//'last_login'=> 'last_login', // (optional)
//'logins' => 'logins' // (optional)
),

'session' => array(
'type' => 'native' // native or database
)
);

view raw a1.php This Gist brought to you by GitHub.

Aquí pongo la descripción de cada elemento:

  • driver: EL ORM que usara (ORM,jelly,mango,sprig[se pueden agregar mas pero hay que modificar el modulo o extenderlo])
  • user_model: tabla(modelo) que se usara ‘user’
  • cost: fuerza (tamaño de la clave) entre 4 y 31, lo ideal es 12, si ponen un numero muy grande tomar en cuenta que consumirá mas recursos.
  • cookie: los datos de la cookie que generara a1, para hacer el login(nombre y duración)
  • columns: aquí le estamos asignando a los items de A1, las columnas de la base de datos, es decir si en su base de datos el nombre de usuarios están en el campo nombre, tendrán que poner ‘username’ => ‘nombre’, modificando lo que esta en el array, los campos opcionales de momento no los usaremos, aunque cabe señalar que solo guardan información extra.
  • session: Aquí es donde definimos el tipo de sesión, de momento y regularmente bastara con dejarlo como esta, si su aplicación correrá en varios servidores conviene ponerlo en modo database(aunque en este caso hay que hacer un par de cosas mas, espero postear pronto sobre esto)
  • .

No se les olvide activar le modulo en el bootstrap, el cual en la sección módulos debería verse así:

<?php
//more code
/**
* Enable modules. Modules are referenced by a relative or absolute path.
*/
Kohana::modules(array(
// 'auth' => MODPATH.'auth', // Basic authentication
// 'cache' => MODPATH.'cache', // Caching with multiple backends
// 'codebench' => MODPATH.'codebench', // Benchmarking tool
         'A1' => MODPATH.'A1', //A1 authentication
'database' => MODPATH.'database', // Database access
// 'image' => MODPATH.'image', // Image manipulation
'orm' => MODPATH.'orm', // Object Relationship Mapping
// 'unittest' => MODPATH.'unittest', // Unit testing
// 'userguide' => MODPATH.'userguide', // User guide and API documentation
));
view raw bootstrap.php This Gist brought to you by GitHub.

Ahora vamos a crear un controlador que sera en este caso el backend, el cual verificara que si estas logueado te deja entrar a ejecutar cualquier acción, si no te re-direccionara a un controlador “account” y al action “login” que tendra la vista del formulario de login, y también tendrá un link a registrarse usando este modulo A1.

En este controlador “backend” tenemos una action, llamada index que cargara un vista, en donde mostraremos un mensaje que dirá “bienvenido Nombre_de_usuario”

<?php defined('SYSPATH') or die('No direct script access.');
/**
* Controller_Backend
*
* Este es el backend de una aplicacion que esta usando A1 como metodo de autenticación
*
* @author Javier
* @package Backend
*
*/
class Controller_Backend extends Controller {

    /**
*
* @var User $auth contains object a1 user
*/
    public $auth;
    
    /**
* (non-PHPdoc)
* @see system/classes/kohana/Kohana_Controller::before()
*/
    public function before()
    {
        parent::before();
        $this->auth = A1::instance();
        $this->_prevent_back();
        if($this->auth->logged_in()=== FALSE)
        {
            $this->request->redirect(Route::url('default',array('controller' => 'account', 'action' => 'login')));
            return;
        }
        view::set_global('logout',Route::url('default',array('controller' => 'account', 'action' => 'logout')));
    }
    
    public function action_index()
    {
        $view = View::factory("backend/home")
                        ->set("auth",$this->auth);
        
        $this->response->body($view);
    }

    private function _prevent_back()
    {
     $this->response->headers("Cache-Control","no-store, no-cache, must-revalidate");
    }
} // End Welcome

view raw backend.php This Gist brought to you by GitHub.

Las partes importantes a destacar son:

  • En el método before asignamos a $this->auth (variable publica) una instancia de A1, que es el modulo que hará el login.
  • Luego preguntamos mediante el método $this->auth->logged_in() que seria lo mismo que usar A1::instance()->logged_in(), que si es FALSE, significa que no esta logeado entonces lo redirecciona a la siguiente url:
  • Route::url(‘default’,array(‘controller’ => ‘account’, ‘action’ => ‘login’) la cual dice: que usando el route llamado default construya un url basada en cierto controlador, cierta acción y cierto id.
  • Tenemos un action (metodo) index que mostrara una vista que estará en la carpeta backend, la vista se llama home y le digo que envié los datos de autenticación usando la variable auth.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
</head>
<body>
<div>
<p>Bienvenido al sistema <strong><?php echo $auth->get_user()->email; ?></strong></p>
<p style="float:right;">
<a href="<?php echo $logout ?>" >Salir</a>
</p>
</div>
</body>
</html>

view raw home.php This Gist brought to you by GitHub.

Este es el código del controlador account, con sus metodos/acciones de registro y login.

<?php defined('SYSPATH') or die('No direct script access.');
/**
* Controller_Account
*
* Aqui estaran las funcionalidades de login y registro
*
* @author Javier
* @package Backend
*
*/
class Controller_Account extends Controller {

    /**
*
* @var User $auth contains object a1 user
*/
    public $auth;
    
    /**
*
* Variable que guardara la sesión
* @var Session $session
*/
    public $session;
    
    /**
*
* Vista que usara el sistema
* @var View|String $view
*/
    public $view;
    
    /**
*
* Aqui se cargara el modelo users
* @var Model_Users $users
*/
    public $users;
    
    /**
* (non-PHPdoc)
* @see system/classes/kohana/Kohana_Controller::before()
*/
    public function before()
    {
        parent::before();
        $this->auth = A1::instance();
        $this->session = Session::instance();
        $this->users = new Model_Users();
    }
    
    public function action_index()
    {
        $this->action_login();
    }
    
    public function action_login()
    {
        $errors = array();
        if($this->request->method()=="POST")
        {
            
            $username = Arr::get($_POST, 'username');
            $password = Arr::get($_POST, 'password');
            $remember = (isset($_POST['remember']))?TRUE:FALSE;
            
            if($this->auth->login($username,$password,$remember))
            {
                $session = $this->auth->session()->as_array();
                $session["user"] = $this->auth->get_user();
                $this->session = $session;
                $this->request->redirect(Route::url('default',array('controller' => 'backend','action' => FALSE)));
            }
            else
            {
                $errors['login'] = "Datos no validos";
            }
        }
        $this->view = View::factory('backend/login')
         ->set('posturl',Route::url('default',array('controller' => 'account', 'action' => 'login')))
         ->set('registerurl',Route::url('default',array('controller' => 'account', 'action' => 'register')))
                            ->set("errors",$errors);
    }
    
    public function action_register()
    {
     $errors = array();
        if($this->request->method()== "POST")
        {
         $username = Arr::get($_POST, 'username');
            $password = Arr::get($_POST, 'password');
            $errors['register'] = "Error al intentar registrarse";
            if($this->users->unique_user($username)=== FALSE)
            {
          $new_user = $this->users->save_user($username,$password,$this->auth);
          if($new_user->saved())
          {
          $this->auth->complete_login($new_user);
          $this->request->redirect(Route::url('default',array('controller' => 'account', 'action' => 'login')).'?saved');
          }
          $errors["register"] = "No se pudo guardar el nuevo registro";
            }
            $errors['register'] = "El usuario {$username} ya existe en la base de datos";
        }
        $this->view = View::factory('backend/register')
         ->set('errors',$errors)
             ->set('posturl',Route::url('default',array('controller' => 'account', 'action' => 'login')))
         ->set('registerurl',Route::url('default',array('controller' => 'account', 'action' => 'register')));
    }
    
    public function action_logout()
    {
     $this->auth->logout();
     $this->request->redirect(Route::url('default',array('controller' => 'backend','action' => FALSE)));
    }
    
    public function after()
    {
        $this->response->body($this->view);
        parent::after();
    }

} // End Account

view raw account.php This Gist brought to you by GitHub.

Como vemos este controlador, tenemos el método before que se ejecuta antes de cualquier “action” en donde establecemos el valor de algunas propiedades de la clase como la autenticación, la sesión y el modelo (entidad).

También le indico que si recibo el método action_index, en realidad ejecute el método action_login, que es el que hará el trabajo.

El método action_login, lo que hace es mostrar la vista login, la cual tiene esta estructura.

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
</head>
<body>
<div>
<h2>Iniciar sesión</h2>
<?php if(isset($errors['login'])):?>
        <p style="border:#F00 1px solid;background:#dd0000;color:#fff;width:auto;">
         <?php echo $errors['login'] ?>
</p>
<?php endif; ?>
<form action="<?php echo $posturl ?>" method="POST">
<p>
<label>Usuario</label>
<input type="text" value="" name="username" />
</p>
<p>
<label>Password</label>
<input type="password" value="" name="password" />
</p>
<p>
<input type="checkbox" name="remember" />
<label>Recordar password</label>
<p>
<input type="submit" value="Iniciar sesión" name="login" >
</p>
</form>
<p>
<a href="<?php echo $registerurl ?>">Registrarse</a>
</p>
</div>
</body>
</html>

view raw login.php This Gist brought to you by GitHub.

Que como vemos, es un simple formulario.

En el mismo método action_index, tiene un if que comprueba si recibe una petición POST, si es así extraemos las variables usando Arr::get de kohana, también verificamos si existe la variable remember, la cual actúa como una FLAG, que en caso de ser verdadero lo usaremos para recordar los datos del usuario.

Aquí es donde ocurre, toda la “magia”, y haremos hincapié en esto.

if($this->auth->login($username,$password,$remember)) :

Este código lo que hace es hacer el login, y el ultimo parámetro remember tiene la capacidad de generar el recordatorio del password.

Si el login es correcto, se creara un sesión y hará la lógica de asignación de variables del usuario a la sesión, posteriormente se redirecciona a el backend del sistema.

En caso de no hacer el login, tenemos anteriormente un array, que en este caso asignara a una clave llamada login, un mensaje de error., el cual se mostrara en la vista login.

También tenemos un método action_register, que posee, un formulario similar al de login, y que en este caso hace el registro de un usuario, usando el modelo users, el cual también hace validación de que no exista un usuario con el mismo nombre(obviamente la validación puede ser diferente).

Y también tenemos el método save_user que es encargado de hacer el insert del nuevo usuario, aquí vemos que usamos para el password, $auth->hash(), que es un método de A1, que lo estamos inyectando como dependencia (DI), y es importante usar este método ya que con esto generamos el password usando el algoritmo de cifrado que incluye A1, aunque lo podremos modificar, pero es indispensable convertir el password al guardarlo, al hacer el login no es necesario convertirlo A1 lo hace en automático.

<?php defined('SYSPATH') or die('No direct script access.');
/**
*
* Model_Users
*
* Modelo/entidad de la tabla users
*
* @author Javier
* @package backend
*/
class Model_Users extends ORM {

/**
*
* Evitamos que convierta mi tabla a plural
* @var boolean $_table_names_plural
*/
protected $_table_names_plural = FALSE;


/**
*
* Comprueba que no exista en la base de datos(evita duplicados)
* @param String $username
* @return boolean
*
*/
public function unique_user($username)
{
$user = ORM::factory($this->_table_name)->where('email','=',$username)->find();
return (bool) $user->loaded();
}

/**
*
* Inserta un nuevo usuario
* @param String $username
* @param String $password
* @param A1 $auth
* @return Model_Users $user
*
*/
public function save_user($username, $password, A1 $auth)
{
$user = ORM::factory($this->_table_name);
$user->email = $username;
$user->password = $auth->hash($password);
$user->save();
return $user;
}

}
view raw users.php This Gist brought to you by GitHub.

Regresando al controlador, después de que el nuevo usuario se haya registrado, se redirecciona al login, si no simplemente avisa del error, mandando esta información, a la vista.

En el mismo controlador tenemos un método action_logout, que lo que hace es cerrar la sesión, y redireccionar, aquí creo que no hace falta mas explicación.

En el método after lo que hago es lanzar la vista a la pantalla usando $this->response->body

Quizá un poco larga la explicación pero es mejor que quede claro.

En resumen, creamos nuestro controlador que haga el login, y otro que cree las cuentas, usamos A1, para manejar el password, para registrar el usuario y para hacer el login, evitando volver a reescribir este proceso cada vez que necesitemos hacer un login.

Como nota adicional, verán en el controlador backend un método privado que se llama: _prevent_back(), este método evita que cuando hagamos logout, al dar clic en el botón atrás, no regrese a la sesión como si nunca hubiéramos salido por que esta cacheado, aunque realmente hayamos finalizado la sesión y no permita hacer nada. Estoy en contacto con el creador del modulo y mande la propuesta de agregar este control de cache al core del modulo, y la parecer en breve ya vendrá por default y ya no tendrán que agregar este método(sera configurable desde el archivo de configuración del modulo).

Tambien me falto agregar que el campo de la base de datos para el password, debe de ser de al menos 70-80 caracteres, o más, por que las cadenas que genera A1, para los passwords son muy grandes, y si aumentan el cost en el config, aun seran mas grandes.

Aparte de los gists, dejo el proyecto en github, para que quien guste lo puede tomarlo y bajarlo:

Hasta el próximo post.

Github: kohana A1 implementación.
Parche para usar A1 pero con sha1 si necesidad de usar bcrypt: https://gist.github.com/1352205
Colocar el archivo del parche (a1.php) en application/classes/ y ahora sus password que generen o usen usaran sha1 y no brcypt con CRYPT_BLOWFISH

2 Responses

  1. Tanta vaina pa’ eso ? y que tan seguro sera !!!
    En CodeIgniter se consigue con su libreria nativa.

Pingbacks/Trackbacks

Leave a Reply

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

*

*

Historico de entradas

febrero 2012
L M X J V S D
« ene    
 12345
6789101112
13141516171819
20212223242526
272829  

Ajaxman

Mi nombre es Javier, soy desarrollador web con especialización en PHP (avanzado), HTML, CSS y Javascript(Medio).

Me considero evangelizador de Kohana Framework, Mozilla Firefox y GNU/Linux Debian.

Estoy casado, y tengo dos hermosos hijos.

Todos los contenidos a menos que se exprese lo contrario estan bajo licencia Creative Commons.

Enlazanos!!

hit counters online counter