Esta semana tuvimos una petición un poco “diferente”. El cliente quería hacer un cambio en la autenticación de usuarios, en una aplicación programada con Symfony 2.

¿Cuál era el problema con la autenticación?

 El problema era que sus usuarios, muchas veces al intentar loguearse, envés de introducir su nombre de usuario, lo copiaban y pegaban directamente al campo de “nombre de usuario” del formulario de login.

Hasta aquí nada demasiado raro. El tema es que copiaban los nombres de usuario con espacios. Para Symfony, tener espacios en el inicio o final del nombre de usuario no es exactamente un problema. La dificultad llega cuando el usuario “pega” dos palabras pensando que su username es una cosa, y esperando que funcione igualmente. Por ejemplo, si su nombre de usuario es “MiUsuario”, pone “Mi Usuario”.

¿Un problema de UX?

Esta acción no funcionaba obviamente y el cliente necesitaba una solución. La solución  no pasaba por actualizar la Interfaz de usuario, ni mejorar la Experiencia de Usuario - por ejemplo añadiendo algún microcopy, o un simple mensaje en pantalla diciendo que el nombre de usuario es incorrecto, o tiene espacios, etc…

No hay mal que por bien...

Este problema me obligó a entrar en las entrañas de Symfony para intentar dar una solución de la forma más limpia y “más Symfony posible”.

Por eso, hoy quiero compartir contigo dos alternativas, que se podrían utilizar al mismo tiempo, para alterar la lógica de autenticación si ya tienes el sistema montado con el FosUserBundle en Symfony y/o estás en la situación en que necesitas utilizar un nombre de usuario no estándar para la autenticación.

Security Guard (Guardia de seguridad)

El componente Guard te proporciona capas de autenticación “con el objetivo de simplificar la creación de sistemas complejos de autenticación”, dándote a ti el control. Es una solución muy interesante si quieres simplificar el desarrollo de formularios de “inicio de sesión”, o un sistema de autenticación de API por token.

¿Cómo funciona?

Encontrarás mucha información "oficial" sobre este tema, pero para que funcione correctamente, lo “único” que tienes que hacer primero es extender la clase AbstractFormLoginAuthenticator de la API de Symfony en la carpeta de tu Bundle ( AppBundle\Security), y cambiar la lógica según lo que necesites. Puedes ver el código aquí.

Básicamente, lo que estás haciendo es crear tu clase de autenticación Guard y esta clase lo que hará será procesar el login con tu propio Firewall.
authentication

Necesitas registrarlo como servicio en el fichero services.yml de tu aplicación. Si no, no funciona.

authentication  

¿Tengo que declarar los argumentos uno a uno?

Envés de declarar los argumentos, podrías declarar “autowire:true”, pero esto, por lo menos a mi, me ha dado muchos problemas. Si sabes por qué, ¿me lo dices? ;)

Y después añadir el ID "app.security.login_form_authenticator" del servicio en el fichero de configuración de seguridad security.yml.

¿Guard y FOSUserBundle sirven para lo mismo?

authentication

Aunque estén pensados para solucionar diferentes problemas,  Guard y FOSUserBundle funcionan bastante bien juntos y lo puedes utilizar por ejemplo, cuando necesites añadir alguna autenticación por API, por ejemplo con el Facebook, o incluir un Captcha en tu formulario de login.

Siempre puedes utilizar las funcionalidades del FOSUserBundle, pero me parece interesante saber que es posible controlar tu sistema de login. Por ejemplo, puedes tener un sistema de login que proteja solamente los endpoints de tu API! Puedes leer más sobre este tema en la documentación de Symfony.

UsernamePasswordFormAuthenticationListener

Creo que el “Security Guard” fue un buen descubrimiento, pero, por lo menos hasta donde llegué, no sirvió para mi problema inicial: “eliminar los espacios entre las cadenas de caracteres”.

¿Cómo comprobar el nombre de usuario antes de la autenticación?

Una opción puede ser aprovechar la capa de abstracción que el Bundle de Seguridad de Symfony ofrece, el SecurityBundle y el Listener “UsernamePasswordFormAuthenticationListener”.

Este listener decide si se puede utilizar la petición actual para autenticar el usuario.

Override

Obviamente que no quieres cambiar el código existente. Entonces, lo que puedes hacer es sustituirlo por tu propia clase “UsernamePasswordFormAuthenticationListener”. Lo “único” que tienes que hacer es, por lo tanto copiar el fichero a tu Bundle, hacer los cambios necesários.
authentication

y declararlo en el fichero de servicios services.yml. authentication

En el método attemptAuthentication() podrás añadir la lógica de autenticación según las necesidades de tu proyecto o aplicación.

Conclusión

Concluyendo, estoy seguro de que haya formas más simples de resolver este problema en este contexto, y que tu contexto necesitaría algo más avanzado, o quizás más simple.

La cuestión es que esta forma de alterar la lógica, permite conocer un poco más el framework - por ejemplo, ¿qué está pasando bajo la capucha de seguridad de Symfony? - lo que es un valor añadido a la hora de tomar decisiones según la demanda del proyecto.

 

¿Cómo hubieras cambiado la lógica de autenticación de tu aplicación Symfony?



Fuentes