Symfony: sistema de usuarios, login y registro

Introducción

Cuando desarrollamos una aplicación, uno de los puntos más importantes y que poseen la mayoría de sitios es el sistema de usuarios. Tenemos que tener especial cuidado en crear un sistema de autenticación robusto y seguro para no poner en entredicho la seguridad de toda la aplicación y de datos sensibles que puedan ser almacenados en ella.

Desarrollar desde cero un sistema de usuarios conlleva mucho trabajo si queremos asegurarnos de que funciona correctamente. De hecho, a día de hoy es algo que debemos tratar de evitar en casi todas las situaciones, salvo que sepamos muy bien lo que estamos haciendo y no haya ninguna otra alternativa fiable y testada que de respuesta a nuestras necesidades, cosa poco probable.

Sistema de usuarios en Symfony

Aquí es donde entra en juego Symfony. Una de las cosas que nos facilita es precisamente desarrollar un sistema de usuarios. Utilizar el sistema de usuarios de este framework tiene dos enormes ventajas respecto a hacerlo nosotros mismos.

En primer lugar, la seguridad de este sistema de usuarios ha sido puesta a prueba durante años en infinidad de aplicaciones diferentes. Si haces las cosas bien y configuras correctamente los recursos protegidos, conseguirás una protección muy superior que la de prácticamente cualquier sistema que puedas desarrollar tú mismo.

Y en segundo lugar, pero no menos importante, ahorrarás un montón de tiempo, ya que cuando aprendas a hacerlo la primera vez, apenas te llevará unos minutos configurar la seguridad de tus aplicaciones en la mayoría de casos. De este modo te olvidas completamente de manejar tú mismo las sesiones a bajo nivel, copiar y pegar un complejo sistema de login de una aplicación a otra para después adaptarlo u otros problemas similares.

Vamos a ver como realizar este proceso. Aunque parezca muy largo, realmente se consigue con poquitas líneas. Es más configurar que desarrollar. Una vez lo tengas hecho puedes adaptarlo como mejor te convenga, cambiando las rutas, la parte protegida, los campos del usuario, las validaciones… todo lo que se te ocurra. Eso sí, espera hasta el final del proceso para probarlo ya que mientras completas lo necesario se producirán errores si intentas cargar la página.

Archivos necesarios

Antes de entrar a explicar cómo se desarrolla, vamos a ver los archivos que tendremos que trastear, que son los siguientes:

  • src/AppBundle/Entity/User.php: será la entidad Usuario de nuestra aplicación. Obviamente puede estar alojada en otro lugar y tener otro nombre, pero para el ejemplo utilizaremos este archivo.
  • app/config/security.yml: en este archivo se encuentra toda la configuración importante y es realmente el principal encargado de que todo funcione correctamente.
  • src/AppBundle/Controller/SecurityController: también necesitaremos algunas acciones en uno de nuestros controladores para dar respuesta a las peticiones, así que crearemos este.
  • app/Resources/views/*: y por supuesto, algunas vistas para mostrar, por ejemplo, el formulario de login o el de registro. Siguiendo las buenas prácticas de Symfony, todas las vistas deberán estar alojadas en este directorio, pero si las alojas dentro de tu bundle también es válido.

Y esto es todo, nuestra clase usuario, nuestro controlador y sus vistas, como siempre, y como extra la configuración de seguridad.

Clase usuario

En primer lugar vamos a crear nuestra clase usuario. Podemos generarlo manualmente o mediante el comando php bin/console doctrine:generate:entityAñadimos todos los campos que deseemos que tenga nuestra clase, pero recuerda que hay dos obligatorios: uno que hará de nombre de usuario, y otro que almacenará la contraseña. En nuestro ejemplo solo utilizaremos esos dos campos, y como nombre de usuario utilizaremos el email. El resultado sería el siguiente:

Una vez lo tenemos, creamos la entidad en la base de datos mediante el comando php bin/console doctrine:schema:update -f.

Ahora empieza lo nuevo. Cuando configuremos nuestra clase como proveedor de usuarios, Symfony necesitará que esta contenga unos métodos extra. Para ello, necesitamos que nuestra clase implemente una interface que nos obligará a crear esos métodos. La interface es UserInterface. La cabecera del archivo quedaría de la siguiente manera:

Ahora nuestro IDE podría informarnos de que faltan métodos de la interface por implementar. Los métodos son los siguientes:

  • getRoles: si nuestra aplicación no trabaja con roles, basta con que devolvamos una cadena con ‘ROLE_USER’ que será el rol por defecto de todos los usuarios.
  • getSalt: más adelante encriptaremos la contraseña, pero el salt ya no es necesario con el método que usaremos, así que simplemente devolvemos null.
  • getUsername: devolvemos el campo que hará de nombre de usuario. Como ya dijimos, en nuestro caso será el email.
  • getPassword: devuelve el campo contraseña. Es probable que ya lo tuviéramos creado si nuestro campo se llama exactamente password.
  • eraseCredentials: por si queremos eliminar información sensible de la entidad. No le daremos uso en el ejemplo.

El resultado de estos cuatro métodos sería el siguiente:

Extra: plainPassword

Pensando ya en el formulario de registro, vamos a dejar nuestra clase preparada para ello. Lo que vamos a hacer es crear un nuevo campo en la clase que se llamará plainPassword. Este campo no se va a almacenar en la base de datos, solamente guardará la contraseña de manera momentánea para poder validarla antes de ser encriptada. Por lo tanto, no añadiremos la anotación @ORM\Column, pero no te olvides del getter y el setter:

Configurando el security.yml

Ya tenemos nuestra clase Usuario, así que el próximo paso será crear la configuración necesaria para proteger nuestra aplicación.

Inicialmente nuestro archivo security luce de la siguiente manera:

Para que todo funcione, tenemos que añadir la configuración para lo siguiente:

  • Definir cuál es nuestra clase Usuario
  • Establecer el algoritmo para cifrar la contraseña
  • Definir qué parte de la aplicación está protegida mediante un firewall
  • Especificar reglas de acceso a diferentes rutas

Parece muy complicado pero realmente son muy poquitas líneas. Vamos a empezar:

Definir la clase Usuario:

Esto lo indicaremos en el apartado providers del archivo, y se hace del siguiente modo:

Como se puede apreciar, bajo este apartado creamos un proveedor, en este caso llamado user_provider, e indicamos que el proveedor es una clase, en concreto AppBundle:User, y el campo de nombre de usuario es el email.

Algoritmo de cifrado

En el mismo archivo definimos esta configuración para nuestra clase. Utilizaremos uno de los más robustos, en este caso bcrypt, y quedaría del siguiente modo:

Crear el firewall

En este apartado simplemente vamos a indicar qué parte de la aplicación está protegida. Al no indicar nada, serán todas las rutas. Además hay que indicar nuestras rutas de login y logout. Quedaría de la siguiente manera:

No hagas caso al firewall dev porque no es importante para la aplicación, simplemente nos permite acceder a algunos recursos cuando estamos en desarrollo.

Hemos definido la ruta de login, la de comprobación del login, que es la misma, y también la de cerrar sesión. La que hay a continuación, target, es a la que redirige la aplicación al cerrar sesión.

Reglas de acceso

En el último apartado del archivo se configuran los requisitos para las diferentes rutas. Mira cómo ha quedado en nuestro ejemplo:

Las reglas superiores tienen más importancia. En la última línea indicamos que todo lo que empieze por ‘/’ necesita que el usuario tenga el rol ‘ROLE_USER’. Como nuestra aplicacónno trabaja con roles, todos los usuarios tienen ese mismo rol. Es decir, para acceder a cualquier ruta es obligatorio ser un usuario logueado.

Sin embargo hay un par de rutas a las que deben poder acceder los usuarios anónimos, es decir, aquellos que no estén logueados. Estas rutas son la de login y la de registro de usuario. Como puedes observar, estas reglas se encuentran más arriba y por lo tanto tienen prioridad sobre la restricción de abajo.

Resultado final de security.yml

Estos cuatro apartados se resumen en pocas líneas. El resultado final de nuestro archivo de seguridad, poniendo todo junto, es el siguiente:

Login

Controlador

Para nuestro login necesitamos solo un action y una vista muy simple, el resto ya lo tenemos configurado. La acción que vamos a usar es la siguiente:

Este código es muy cortito. Si te fijas, no hay una comprobación de nombre y contraseña y tampoco una redirección si hay éxito en el login. Esto lo maneja todo internamente Symfony, si sale bien automáticamente te redirige a la ruta indicada. Las líneas que hay solo son para recuperar información del último nombre y error producido si lo hubiera.

Vista

La vista asociada es muy simple también. Un formulario hecho a mano. Podrías insertarlo en cualquier lugar que quisieras, pero en nuestro ejemplo es lo único que se va a mostrar en esta página. El código es el siguiente:

Puedes personalizar esta vista tanto como quieras. Lo importante es que el campo que hace de nombre de usuario tenga el atributo name como ‘_username’ y el campo contraseña tenga también name=”_password”. Además la ruta que maneja esta formulario es también la de login.

Logout

El cierre de sesión es lo más simple de hacer. En nuestro security.yml indicamos que la ruta sería ‘/logout’. Lo único que nos falta es crear la ruta con la acción vacía, ya que esto lo maneja Symfony automáticamente y realmente no ejecuta el código de la acción asociada a la ruta. Si usas el formato YML para las rutas no sería necesario ni crear la acción, pero como en este ejemplo usamos anotaciones crearemos una acción vacía:

Para que el usuario salga de la aplicación solo le hace falta acceder a esta ruta. Se lo facilitaremos añadiendo en todas las páginas donde esté logueado un enlace para cerrar sesión:

Registro

Ya tenemos el sistema de usuarios funcional, pero nos queda un apartado importante. Salvo que vayamos a introducir los usuarios a mano en la base de datos, necesitamos habilitar una ruta para que los usuarios se puedan registrar en la aplicación.

Si estás leyendo este artículo lo normal es que ya sepas crear formularios con Symfony y usarlos para guardar objetos en la base de datos. El registro de usuarios es tan fácil como eso. La única dificultad añadida es que necesitamos encriptar la contraseña antes de guardarla.

Recuerda que en la entidad usuario utilizamos un campo extra llamado plainPassword que es con el que vamos a trabajar para el registro.

Formulario de usuario

Lo primero es crear el formulario de la entidad. Podemos hacerlo con el comando php bin/console doctrine:generate:form AppBundle:User, y modificarlo un poco para conseguir el siguiente resultado:

Fíjate que no hemos añadido el campo password al formulario sino el de contraseña en texto plano. Además, hemos escogido un tipo de campo especial que es RepeatedType. Este campo muestra dos inputs para esa propiedad y comprueba por sí solo que ambos valores coincidan, para obligar al usuario a introducir la contraseña dos veces y así evitar errores.

Controlador

Falta la acción que mostrará el formulario. Como hemos comentado, es tan simple como cualquier otra de este tipo, solamente con el añadido de encriptar la contraseña antes de guardar el usuario:

Vista

La vista es tan simple como volcar el formulario entero en el lugar que deseemos:

Finalizando

Ya tenemos un sistema de usuarios totalmente funcional. Salvo login y registro, todas las rutas de la aplicación exigen que el usuario esté logueado para poder acceder. En nuestra aplicación hemos creado una página principal en la ruta ‘/’, que solo se puede acceder tras el login.

Haz la prueba con tu código: accede a ‘/registro’ y crea un nuevo usuario. Después en el login entra con el usuario que has creado y podrás acceder a las demás rutas. Ahora cierra sesión y trata de acceder a una ruta protegida. Si lo has hecho bien solo podrías ver el login, que es donde te redirige al tratar de acceder a un recurso protegido.

Insisto, como dije al inicio del artículo, en que es mucho más fácil de lo que pueda parecer si nunca te has enfrentado a un sistema de usuarios de este tipo.

Si después de finalizar el artículo y seguir todos los pasos te da algún error, déjame un comentario con tu problema para intentar solucionarlo.

Fuentes