Buy Access to Course
04.

Manejo de errores de autenticación

|

Share this awesome video!

|

Cuando iniciamos sesión con un correo electrónico y una contraseña no válidos, parece que el sistema json_logindevuelve un bonito JSON con una clave error establecida en "Credenciales no válidas". Si quisiéramos personalizar esto, podríamos crear una clase que implementeAuthenticationFailureHandlerInterface:

class AppAuthFailureHandler implements AuthenticationFailureHandlerInterface
{
    public function onAuthenticationFailure($request, $exception)
    {
        return new JsonResponse(
            ['something' => 'went wrong'],
            401
        );
    }
}

Y luego establecer su ID de servicio en la opción failure_handler en json_login:

json_login:
    failure_handler: App\Security\AppAuthFailureHandler

Mostrar el error en el formulario

Pero, esto nos sirve de sobra. Así que vamos a utilizarlo en nuestro/assets/vue/LoginForm.vue. No profundizaremos demasiado en Vue, pero ya tengo un estado llamado error, y si lo configuramos, se mostrará en el formulario:

97 lines | assets/vue/LoginForm.vue
// ... lines 1 - 48
<script setup>
// ... lines 50 - 54
const error = ref('');
// ... lines 56 - 65
const handleSubmit = async () => {
// ... line 67
error.value = '';
// ... lines 69 - 82
if (!response.ok) {
const data = await response.json();
console.log(data);
// TODO: set error
return;
}
// ... lines 90 - 93
}
</script>

Después de hacer la petición, si la respuesta no está bien, ya estamos descodificando el JSON. Ahora digamos que error.value = data.error:

96 lines | assets/vue/LoginForm.vue
// ... lines 1 - 48
<script setup>
// ... lines 50 - 65
const handleSubmit = async () => {
// ... lines 67 - 82
if (!response.ok) {
const data = await response.json();
error.value = data.error;
return;
}
// ... lines 89 - 92
}
</script>

Para ver si funciona, asegúrate de que tienes Webpack Encore ejecutándose en segundo plano para que recompile nuestro JavaScript. Actualiza. Y... puedes hacer clic en este pequeño enlace para hacer trampas e introducir un correo electrónico válido. Pero luego escribe una contraseña ridícula y... ¡Me encanta! ¡Vemos "Credenciales no válidas" en la parte superior con unos recuadros rojos!

json_login Requires Content-Type: application/json

Así que la llamada AJAX funciona de maravilla. Sin embargo, hay un problema con el mecanismo de seguridad json_login: requiere que envíes una cabecera Content-Type configurada comoapplication/json. Nosotros lo establecemos en nuestra llamada Ajax y tú también deberías hacerlo:

96 lines | assets/vue/LoginForm.vue
// ... lines 1 - 48
<script setup>
// ... lines 50 - 65
const handleSubmit = async () => {
// ... lines 67 - 69
const response = await fetch('/login', {
// ... line 71
headers: {
'Content-Type': 'application/json'
},
// ... lines 75 - 78
});
// ... lines 80 - 92
}
</script>

Pero... si alguien se olvida, queremos asegurarnos de que las cosas no se vuelven completamente locas.

Comenta esa cabecera Content-Type para que podamos ver qué ocurre:

96 lines | assets/vue/LoginForm.vue
// ... lines 1 - 48
<script setup>
// ... lines 50 - 65
const handleSubmit = async () => {
// ... lines 67 - 69
const response = await fetch('/login', {
// ... line 71
headers: {
//'Content-Type': 'application/json'
},
// ... lines 75 - 78
});
// ... lines 80 - 92
}
</script>

Luego muévete, actualiza la página... escribe una contraseña ridícula y... ¿se borra el formulario? Mira la llamada a la Red. ¡La ruta devolvió un código de estado 200 con una clave user establecida en null!

Y... ¡eso tiene sentido! Como nos falta la cabecera, el mecanismo json_login no hizo nada. En su lugar, la petición continuó a nuestro SecurityController... excepto que esta vez el usuario no está conectado. Así que devolvemos user: null... con un código de estado 200.

Esto es un problema porque hace que parezca que la llamada Ajax ha tenido éxito. Para solucionarlo, si, por cualquier motivo, se omitió el mecanismo json_login... pero el usuario accede a nuestra ruta de inicio de sesión, devolvamos un código de estado 401 que diga:

¡Oye! ¡Necesitas iniciar sesión!

Entonces, si no es $user, entonces return $this->json()... y esto podría parecerse a cualquier cosa. Incluyamos una clave error que explique lo que probablemente salió mal: esto coincide con la claveerror que json_login devuelve cuando fallan las credenciales, así que a nuestro JavaScript le gustará esto. Caramba. ¡Incluso corregiré mi errata!

26 lines | src/Controller/SecurityController.php
// ... lines 1 - 9
class SecurityController extends AbstractController
{
// ... line 12
public function login(#[CurrentUser] $user = null): Response
{
if (!$user) {
return $this->json([
'error' => 'Invalid login request: check that the Content-Type header is "application/json".',
], 401);
}
// ... lines 20 - 23
}
}

Y lo más importante, para el segundo argumento, pasa un 401 para el código de estado.

A continuación, podemos simplificar... porque ahora sabemos que habrá un usuario:

26 lines | src/Controller/SecurityController.php
// ... lines 1 - 9
class SecurityController extends AbstractController
{
// ... line 12
public function login(#[CurrentUser] $user = null): Response
{
if (!$user) {
return $this->json([
'error' => 'Invalid login request: check that the Content-Type header is "application/json".',
], 401);
}
return $this->json([
'user' => $user->getId(),
]);
}
}

¡Hermoso! Gira y envía otra contraseña incorrecta. ¡Precioso! El código de estado 401 activa nuestro código de gestión de errores, que muestra el error en la parte superior. Maravilloso.

Vuelve a LoginForm.vue y pon de nuevo la cabecera Content-Type:

96 lines | assets/vue/LoginForm.vue
// ... lines 1 - 48
<script setup>
// ... lines 50 - 65
const handleSubmit = async () => {
// ... lines 67 - 69
const response = await fetch('/login', {
// ... line 71
headers: {
'Content-Type': 'application/json'
},
// ... lines 75 - 78
});
// ... lines 80 - 92
}
</script>

Siguiente: vamos a iniciar sesión con éxito y... ¡a averiguar qué queremos hacer cuando eso ocurra! También vamos a hablar de la sesión y de cómo autentica nuestras peticiones a la API.