Un sistema de autenticación de usuarios es una parte común de muchas aplicaciones web. En CakePHP hay muchas formas para autenticar usuarios, cada una de estas provee diferentes opciones. La esencia de componente de autenticación es comprobar si el usuario tiene una cuenta con el sitio. De ser así, el componente da al usuario acceso completo a sitio.
Este componente se puede combinar con el componente ACL (access control lists) para crear niveles más complejos de acceso al sitio. El componente ACL, por ejemplo, podría permitir acceso a un usuario a áreas publicas del sitio, mientras que concede a otro usuario acceso a porciones administrativas protegidas del sitio.
El AuthComponent de CakePHP se puede usar para crear un sistema fácil y rápidamente. Veamos como construir un sistema de autenticación simple.
Al igual que todos los componentes, se utiliza mediante la incorporación de “Auth” a la lista de componentes en el controlador:
class FooController extends AppController {
var $components = array('Auth');
O añadelo al AppController si todos tus controladores lo van a usar:
class AppController extends Controller {
var $components = array('Auth');
Ahora, hay unas pocas convenciones en las que pensar cuando se usa el AuthComponent. Por defecto, el AuthComponent espera que se tenga una tabla llamada “users” con campos llamados “username” y “password”. En algunos casos, las bases de datos no permiten usar “password” como nombre de columna, mas tarde, veremos como cambiar el nombre por defecto de los campos para trabajar con nuestro propio entorno.
Vamos a crear nuestra tabla “users” usando el siguiente SQL:
CREATE TABLE users (
id integer auto_increment,
username char(50),
password char(50),
PRIMARY KEY (id)
);
Algo a tener en cuenta a la hora de crear una tabla para almacenar todos los datos de autenticación del usuario es que el AuthComponent espera el valor del password almacenado esté encriptado en vez de estar almacenado en texto plano. Asegúrese de que el campo que utilizará para almacenar la contraseña sea suficientemente largo para almacenar el hash (40 caracteres para SHA1, por ejemplo).
Para la configuración más básica usted solo tiene que crear dos acciones en el controlador:
class UsersController extends AppController {
var $name = 'Users';
var $components = array('Auth'); //No es necesario si se declaro en el app controller
/**
* El AuthComponent proporciona la funcionalidad necesaria
* para el acceso (login), por lo que se puede dejar esta función en blanco.
*/
function login() {
}
function logout() {
$this->redirect($this->Auth->logout());
}
}
Si bien usted puede dejar la función login() en blanco, necesitara crear la vista para la acción login (guardela en app/views/users/login.ctp). Esta es la única vista del UsersController que es necesario crear, sin embargo. El siguiente ejemplo asume que ya está familiarizado con el uso del Form helper:
<?php
echo $session->flash('auth');
echo $form->create('User', array('action' => 'login'));
echo $form->input('username');
echo $form->input('password');
echo $form->end('Login');
?>
Esta vista crea un simple formulario de login en el cual introducir el nombre de usuario y la clave. Una vez enviado este formulario, el AuthComponent se encargará del resto por usted. El session flash message mostrara cualquier información generada por el AuthComponent.
Lo creas o no, ya está! Esta es la manera de implementar una increiblemente simple, base de datos de autenticación usando el componente Auth. Sin embargo, hay mucho más que podemos hacer. Echemos un vistazo a algunos usos más avanzados del componente.
Para cambiar las opciones predeterminadas de AuthComponent tienes que crear el método beforeFilter() en el controlador, llamar a varios métodos predefinidos, y configurar algunas variables del componente.
Para cambiar el nombre del campo que se utiliza para guardar las contraseñas, “password”, a “secretword”, por ejemplo, haríamos lo siguiente:
class UsersController extends AppController {
var $components = array('Auth');
function beforeFilter() {
$this->Auth->fields = array(
'username' => 'username',
'password' => 'secretword'
);
}
}
En este caso, ¡no olvidemos que también hay que cambiar en la vista el nombre del campo!
Las variables del componente Auth también se utilizan para que los usuarios que no han entrado en el sistema puedan acceder a determinados métodos.
Por ejemplo, si queremos que todos los usuarios puedan acceder solamente a los métodos index y view, hacemos lo siguiente:
function beforeFilter() {
$this->Auth->allow('index','view');
}
Con el objetivo de desplegar los mensajes de error que la Autentificacion muestra, necesitas añadir el siguiente codigo en tu vista. En este caso, el mensaje aparecerá debajo de los mensajes flash regulares:
<?php
if ($session->check('Message.flash')) {
$session->flash();
}
if ($session->check('Message.auth')) {
$session->flash('auth');
}
?>
A veces puede ser difícil diagnosticar problemas cuando encuentras comportamientos inesperados. Recordar estos puntos te puede ayudar.
Password hashing
Al enviar información a través de un formulario, el componente Auth encripta automáticamente el contenido del campo contraseña, si también hay datos en el campo nombre de usuario. Así que si estás intentando crear algún tipo de página de registro de nuevo usuario, asegúrate de que el usuario rellene un campo «confirmar contraseña» que puedas comparar. Aquí va un código de ejemplo:
<?php
function register() {
if ($this->data) {
if ($this->data['User']['password'] == $this->Auth->password($this->data['User']['password_confirm'])) {
$this->User->create();
$this->User->save($this->data);
}
}
}
?>
Loop Infinito
Otro proplema que puede ocurir es un Loop infinito que termina por dar el siguiente Error en el navegador:
Error 310 (net::ERR_TOO_MANY_REDIRECTS): Demasiados redireccionamientos.
Esto ocurre cuando por ejemplo tenemos un element que recibe datos de algún método de un controlador, como la ejecución de este método requiere que se realice el login, se creara un Loop infinito que finalmente hará que el Navegador rechace las redirecciones. Para solucionarlo debemos agregar :
function beforeFilter() {
$this->Auth->allow('listMenu');
}
Al controlador que este enviando datos al element a través del método (en este caso) listMenu.
The automatic hashing of your password input field happens only if posted data also contains username and password fields
When posting information to an action via a form, the Auth component automatically hashes the contents of your password input field if posted data also contains username field. So, if you are trying to create some sort of registration page, make sure to have the user fill out a “confirm password” field so that you can compare the two. Here’s some sample code:
<?php
function register() {
if ($this->data) {
if ($this->data['User']['password'] == $this->Auth->password($this->data['User']['password_confirm'])) {
$this->User->create();
$this->User->save($this->data);
}
}
}
?>
AuthComponent usa la clase Security para encriptar una contraseña. La
clase Security usa el esquema SHA1 por defecto. Para cambiar a otra
función hash usada por el componente Auth, usa el método setHash
pasándole md5
, sha1
o sha256
como primer y único parámetro.
Security::setHash('md5'); // o sha1 o sha256.
La clase Security usa el valor de inicialización (salt value, que se encuentras en /app/config/core.php) para el hashing de la contraseña.
Si quieres usar una lógica diferente para el hashing de la contraseña
más allá de md5/sha1 con el valor salt de la aplicacion, necesitará
reescribir el mecanismo estandar hashPassword - podrías necesitar hacer
esto si, por ejemplo, tuvieses una base de datos existente que
anteriormente usaba un esquema de hashing sin un valor de salt. Para
hacer esto, crea el metodo hashPasswords
en la clase que quieras que
se haga a cargo del hashing de las contraseñas (normalmente el modelo
User ) y establece el atributo authenticate
de Auth al objeto contra
el que se está autenticando (normalmente es User) de este modo:
function beforeFilter() {
$this->Auth->authenticate = ClassRegistry::init('User');
...
parent::beforeFilter();
}
Con el código anterior, el método hashPasswords()
del modelo User
será llamado cada vez que Cake llame a
AuthComponent::hashPasswords()
. Aquí está un ejemplo del método
hashPasswords
, apropiado si ya tienes una tabla de usuarios repleta
de contraseñas de hash “plain md5”:
class User extends AppModel {
function hasPasswords($data) {
if (isset($data['User']['password'])) {
$data['User']['password'] = md5($data['User']['password']);
return $data;
}
return $data;
}
}
action (string $action = ':controller/:action')
Si estas usando ACOs como parte de tu estructura ACL, puedes obtener la ruta al nodo del ACO que está enlazado a un par controlador/acción particular.
$acoNode = $this->Auth->action('users/delete');
Si no le pasas valores, utilizará el par controlador/acción actual (el que se está ejecutando).
Si tienes acciones en tu controlador que no necesitas que se autentiquen contra ellas (como una acción de registro de usuarios), puedes agregar métodos que debe ignorar AuthComponent. El siguiente ejemplo muestra como permitir una acción llamada “register”.
function beforeFilter() {
...
$this->Auth->allow('register');
}
Si deseas permitir que múltiples acciones no usen autenticación, las pasas como parámetros al método allow():
function beforeFilter() {
...
$this->Auth->allow('foo', 'bar', 'baz');
}
Atajo: también puedes permitir todas las acciones en un controlador usando “*”.
function beforeFilter() {
...
$this->Auth->allow('*');
}
Si estás usando requestAction en tu layout o en tus elementos, deberías permitir esas acciones para poder abrir la página de login correctamente.
El componente auth supone que tus nombres de acciones siguen las convenciones y usan guiones bajos.
Habrá algunas veces que quieras eliminar acciones de la lista de acciones permitidas (añadidas usando $this->Auth->allow()). He aquí un ejemplo:
function beforeFilter() {
$this->Auth->authorize = 'controller';
$this->Auth->allow('delete');
}
function isAuthorized() {
if ($this->Auth->user('role') != 'admin') {
$this->Auth->deny('delete');
}
...
}
hashPasswords ($data)
Este método verifica si $data
contiene los campos nombre de
usuario(username) y contraseña(password), tal y como está
especificado en la variable $fields
indexados por el nombre del
modelo especificado en $userModel
. Si el array $data
contiene
el nombre de usuario y la contraseña, realiza el hash del campo
contraseña en el array y devuelve el array $data
con el mismo
formato. Esta función debe ser usada antes de realizar llamadas de
inserción o actualización de los datos del usuario cuando afecta al
campo contraseña.
$data['User']['username'] = '[email protected]';
$data['User']['password'] = 'changeme';
$hashedPasswords = $this->Auth->hashPasswords($data);
pr($hashedPasswords);
/* devuelve:
Array
(
[User] => Array
(
[username] => [email protected]
[password] => 8ed3b7e8ced419a679a7df93eff22fae
)
)
*/
En el campo $hashedPasswords[“User”][“password”] ahora debería ser
realizado el “hash” usando el método password
del componente.
Si tu controlador usa el compoente Auth y los datos recibidos por POST contienen los campos explicados arriba, automáticamente realizará el hash al campo contraseña usando esta función.
Si estás utilizando Acl en modo CRUD, tal vez desees asignar ciertas acciones no predeterminadas a cada parte de CRUD.
$this->Auth->mapActions(
array(
'create' => array('ciertaAccion'),
'read' => array('ciertaAccion', 'ciertaAccion2'),
'update' => array('ciertaAccion'),
'delete' => array('ciertaAccion')
)
);
login($data = null)
Si estás haciendo algún tipo de login basada en Ajax, puedes usar este
método para identificar manualmente a alguien en el sistema. Si no pasas
ningún valor para $data
, automáticamente usará los datos enviados
mediante POST al controlador.
Por ejemplo, en una aplicación tal vez desees asignar a un usuario una contraseña y autoidentificarlo en el sistema tras el registro. En un ejemplo muy simplificado:
Vista:
echo $form->create('User',array('action'=>'registrar'));
echo $form->input('username');
echo $form->end('Regístrame');
Controlador:
function registrar() {
if(!empty($this->data)) {
$this->User->create();
$contrasena_asignada = "ConTr4senna";
$this->data['User']['password'] = $contrasena_asignada;
if($this->User->save($this->data)) {
// enviar el email de registro conteniendo la contraseña al nuevo usuario
$this->Auth->login($this->data);
$this->redirect("inicio");
}
}
Una cosa a remarcar es que has de redirigir manualmente al usuario tras
el login ya que no se invoca loginRedirect()
.
$this->Auth->login($data)
devuelve 1 tras un login exitoso, 0 en
caso de fallo.
Provee de una manera rápida de “deautenticar” a alguien y redirigirlo a donde necesite ir. Este método también es útil si deseas proporcionar un enlace “Cerrar sesión” dentro de una sección para usuarios registrados de tu aplicación.
Ejemplo:
$this->redirect($this->Auth->logout());
password (string $password)
Pásale una cadena de texto, y obtendrás la contraseña “hasheada”. Esta es una funcionalidad esencial si estás creando una pantala de registro de usuario donde los usuarios han de insertar sus contraseñas una segunda vez para confirmarlas. if ($this->data[“User”][“password”] == $this->Auth->password($this->data[“User”][“password2”])) { // Las contraseñas concuerdan, continuar procesando … } else { $this->flash(“Las contraseñas introducidas no concuerdan”, “users/registrar”); }
El componente Auth automáticamente aplicará el hash al campo contraseña (password) si también está presente el campo nombre de usuario (username) en los datos recibidos en la petición.
Cake añade tu cadena contraseña a un valor salt y después realiza el
hash. La función de hash utilizada depende de la seleccionada por la
clase utilidad del núcleo Security
(sha1 por defecto). Puedes
utilizar la función Security::setHash
para cambiar el método para
calcular el hash. El valor salt es el indicado en la configuración
de tu aplicación definido en tu core.php
.
user(string $key = null)
Este método proporciona información sobre el usuario actualmente identificado. La información es tomada de la sesión. Por ejemplo:
if ($this->Auth->user('rol') == 'admin') {
$this->flash('Tienes acceso de administrador');
}
También puede ser usado para obtener todos los datos de sesión del usuario así:
$data['User'] = $this->Auth->user();
Si este método devuelve null
es que el usuario no se ha identificado
(logged in).
En la vista puedes utilizar el helper Session
para obtener la
información del usuario actualmente autenticado:
$session->read('Auth.User'); // devuelve el registro completo del usuario
$session->read('Auth.User.nombre') //devuelve el valor particular de un campo
La clave de la sesión puede ser diferente dependiendo de qué modelo se
ha configurado para ser utilizado por Auth. P.e., si usas el modelo
Cuenta
en vez de User
, entonces la clave de sesión sería
Auth.Cuenta
.
Ahora hay varias variables relacionadas con Auth que también puedes
utilizar. Normalmente añades esta configuración en el método
beforeFilter() de tu controlador. Si necesitas aplicar dicha configuración a todo el sitio, deberías añadirla a beforeFilter()
de AppController
.
¿No deseas utilizar un modelo User contra el que autenticar? No hay problema. Simplemente cámbialo configurando este valor con el nombre del modelo que deseas usar.
<?php
$this->Auth->userModel = 'Miembro';
?>
Sobreescribe los campos de usuario y contraseña por defecto usados para la autenticación.
<?php
$this->Auth->fields = array('username' => 'email', 'password' => 'passwd');
?>
Utiliza esto para añadir requisitos adicionales para que la autenticación sea exitosa.
<?php
$this->Auth->userScope = array('User.activo' => true);
?>
Puedes cambiar el login por defecto de /users/login para que sea cualquier acción a tu elección.
<?php
$this->Auth->loginAction = array('admin' => false, 'controller' => 'miembros', 'action' => 'inicio_sesion');
?>
El componente AuthComponent recuerda qué par controlador/acción estabas
tratando de ejecutar antes de que pedirte que te autenticaras,
almacenando el valor en Session bajo la clave Auth.redirect
. Sin
embargo, si este valor de la sesión no está definido (si vienes de la
página de login de un enlace externo, por ejemplo), entonces el usuario
será redirigido a la URL indicada en loginRedirect.
Ejemplo:
<?php
$this->Auth->loginRedirect = array('controller' => 'miembros', 'action' => 'inicio');
?>
You can also specify where you want the user to go after they are logged out, with the default being the login action.
<?php
$this->Auth->logoutRedirect = array(Configure::read('Routing.admin') => false, 'controller' => 'members', 'action' => 'logout');
?>
Cambia el mensaje de error por defecto que se mostrará, cuando el login no sea exitoso.
<?php
$this->Auth->loginError = "No, you fool! That's not the right password!";
?>
Cambia el mensaje de error por defecto que será mostrado, cuando intenten acceder a un objeto o a una acción a la que no autorizada.
<?php
$this->Auth->authError = "Sorry, you are lacking access.";
?>
Normally, the AuthComponent will automatically redirect you as soon as it authenticates. Sometimes you want to do some more checking before you redirect users:
<?php
function beforeFilter() {
...
$this->Auth->autoRedirect = false;
}
...
function login() {
//-- code inside this function will execute only when autoRedirect was set to false (i.e. in a beforeFilter).
if ($this->Auth->user()) {
if (!empty($this->data['User']['remember_me'])) {
$cookie = array();
$cookie['username'] = $this->data['User']['username'];
$cookie['password'] = $this->data['User']['password'];
$this->Cookie->write('Auth.User', $cookie, true, '+2 weeks');
unset($this->data['User']['remember_me']);
}
$this->redirect($this->Auth->redirect());
}
if (empty($this->data)) {
$cookie = $this->Cookie->read('Auth.User');
if (!is_null($cookie)) {
if ($this->Auth->login($cookie)) {
// Clear auth message, just in case we use it.
$this->Session->delete('Message.auth');
$this->redirect($this->Auth->redirect());
}
}
}
}
?>
The code in the login function will not execute unless you set $autoRedirect to false in a beforeFilter. The code present in the login function will only execute after authentication was attempted. This is the best place to determine whether or not a successful login occurred by the AuthComponent (should you desire to log the last successful login timestamp, etc.).
With autoRedirect set to false, you can also inject additional code such as keeping track of the last successful login timestamp
<?php
function login() {
if( !(empty($this->data)) && $this->Auth->user() ){
$this->User->id = $this->Auth->user('id');
$this->User->saveField('last_login', date('Y-m-d H:i:s') );
$this->redirect($this->Auth->redirect());
}
}
?>
Name of the session array key where the record of the current authed user is stored.
Defaults to «Auth», so if unspecified, the record is stored in «Auth.{$userModel name}».
<?php
$this->Auth->sessionKey = 'Authorized';
?>
Si estás haciendo solicitudes basadas en Ajax o Javascript que requieren sesiones autenticadas, establece en esta variable el nombre del elemento vista que deseas mostrar y retornar cuando la sesión es inválida o ha expirado.
Como con cualquier parte de CakePHP, asegúrate de revisar la clase AuthComponent para mayores detalles.
This variable holds a reference to the object responsible for hashing passwords if it is necessary to change/override the default password hashing mechanism. See Changing the Encryption Type for more info.
If using action-based access control, this defines how the paths to action ACO nodes is computed. If, for example, all controller nodes are nested under an ACO node named “Controllers”, $actionPath should be set to “Controllers/”.
In case you want to have another layout for your Authentication error message you can define with the flashElement variable that another element will be used for display.
<?php
$this->Auth->flashElement = "message_error";
?>
In this newly defined element to ensure your authError
and
loginError
messages are displayed ensure you echo $message
.
Here’s an example:
// Code in /app/views/elements/message_error.ctp
<div class="ui-state-error">
<?php echo $message; ?>
</div>
Now authError
& loginError
messages will be displayed using
jQuery UI’s custom theme. Obviously, you can change the HTML element to
fit whatever need you have. The important thing here being that the
$message
variable was echo’d and the user will see the appropriate
information…instead of a blank div
.
Set the default allowed actions to allow if setting the component to “authorize” => “controller”
var $components = array(
'Auth' => array(
'authorize' => 'controller',
'allowedActions' => array('index','view','display');
)
);
index, view, and display actions are now allowed by default.