Indíce de contenidos
Introducción
En el mundo del desarrollo web moderno, la autenticación es una de las piedras angulares para garantizar la seguridad y la experiencia del usuario. Sin embargo, a menudo nos encontramos abrumados por una multitud de paquetes y soluciones complejas que dificultan más que simplifican el proceso. En este artículo, exploraremos cómo implementar un sistema de autenticación robusto y sin complicaciones utilizando las herramientas poderosas pero elegantes de Laravel 11, Vue 3 e Inertia JS.
Videos
En este post vemos los 3 puntos linealmente, pero al grabar los videos lo realice por separado, por lo que aquí les colocare los videos que genere de este post
Creación de componentes
Para empezar, necesitamos crear un componente de registro. En este componente, el usuario introducirá su nombre, apellidos, correo u otros datos necesarios. El primer paso es generar un diseño para los componentes de autenticación que vayamos a crear. Para ello, crearemos un archivo llamado AuthLayout.vue dentro de la carpeta resources/js/Components/AuthLayout.vue y añadiremos el siguiente código:
<template>
<div>
<main>
<slot></slot>
</main>
<footer style="margin-top: 20px;">Footer de AuthLayout</footer>
</div>
</template>
<script>
export default {
name: "AuthLayout",
};
</script>
Aunque este layout no sea crítico, es necesario incluirlo. En el proyecto base para esta tutorial, esta configuración está presente. Si no definimos un layout específico, se asignará el layout por defecto. Por lo tanto, es importante definir el layout que más se vaya a utilizar en la aplicación como el layout por defecto.
Continuaremos creando varias vistas, comenzando por la vista de registro. Para ello, crearemos un archivo llamado Registro.vue en la siguiente ruta: /resources/js/Pages/Registro.vue y añadiremos el siguiente código
<template>
<div>
<form @submit.prevent="enviarFormulario" style="display: block">
<label for="nombre">Nombre</label>
<input type="text" v-model="nombre" name="nombre" />
<br />
<label for="email">Email</label>
<input type="email" v-model="email" name="email" />
<br />
<label for="password">Password</label>
<input type="password" v-model="password" name="password" />
<br />
<button type="submit">Registrarme</button>
</form>
</div>
</template>
<script>
import { router } from "@inertiajs/vue3";
import AuthLayout from "./../Components/AuthLayout.vue";
export default {
name: "Registro",
layout: AuthLayout,
data: () => ({
nombre: "",
email: "",
password: ""
}),
methods: {
enviarFormulario() {
axios
.post("/registro", { //Se envia el formulario al servidor
nombre: this.nombre,
email: this.email,
password: this.password,
})
.then((res) => {
/* Si responde satisfactoriamente es por que se genero
correctamente el usuario y se le dirigira al login
a iniciar sesión */
router.get("/login");
})
.catch((err) => {
/* En caso de error se mostrara la alerta */
alert("Error al registrar al usuario");
});
},
},
};
</script>
Ahora procederemos a generar el componente para el inicio de sesión. Lo crearemos en la siguiente ubicación: /resources/js/Pages/Login.vue y añadiremos el siguiente código:
<template>
<div>
<form @submit.prevent="enviarFormulario">
<label for="email">Email</label>
<input type="email" v-model="email" name="email" />
<br/>
<label for="password">Password</label>
<input type="password" v-model="password" name="password" />
<br/>
<button type="submit">Iniciar sesión</button>
</form>
</div>
</template>
<script>
import { router } from "@inertiajs/vue3";
import AuthLayout from "./../Components/AuthLayout.vue"
export default {
name: "Login",
layout: AuthLayout,
data: () => ({
email: "",
password: "",
}),
methods: {
enviarFormulario() {
axios
.post("/login", {
email: this.email,
password: this.password,
})
.then((res) => {
router.get("/inicio");
})
.catch((err) => {
alert("Error al iniciar sesión");
});
},
},
};
</script>
Ahora crearemos el componente de Inicio, que es la vista que se le mostrará al usuario después de un inicio de sesión exitoso. Este componente estará ubicado en /resources/js/Pages/Inicio.vue y contendrá el siguiente código:
<template>
<div>
<div>Bienvenido {{ user.name }}</div>
<p>Tu correo es {{ user.email }}</p>
<Link href="/logout">Cerrar Sesión</Link>
</div>
</template>
<script>
export default {
name: "Inicio",
props: {
user: Object,
},
};
</script>
Con esto ya hemos terminado del lado del frontend, ahora vamos al backend.
Creación de controladores
Para mantener una organización adecuada, crearemos un controlador para gestionar la autenticación, llamado AuthController, y otro para manejar las vistas del inicio, llamado FrontController.
php artisan make:controller AuthController
php artisan make:controller FrontController
En el controlador de AuthController colocaremos lo siguiente:
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Inertia\Inertia;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
function registro(){
return Inertia::render('Registro');
}
function registroPost(Request $request)
{
$request = $request->validate([
'nombre' => 'required|string',
'email' => 'required|string|email|unique:users,email',
'password' => 'required|string',
]);
$user = User::create([
'name' => $request['nombre'],
'email' => $request['email'],
'password' => Hash::make($request['password']),
]);
/* Se puede autenticar al usuario de una vez y desde el comente de registro
redirigirlo al inicio, pero en este caso quiero redirigirlo a la página de
inicio de sesión
Utilizar la siguiente linea si se quiere autenticar luego de registrar
Auth::loginUsingId($user);
*/
return response()->json([], 201);
}
function login(){
return Inertia::render('Login');
}
function loginPost(Request $request){
//Se hace una validacion de los datos de la petición
$credenciales = $request->validate([
'email' => 'required|email',
'password' => 'required'
]);
//Verificamos las credenciales
if (Auth::attempt($credenciales)) {
$request->session()->regenerate();
return response()->json([], 200);
}
//Si llegamos aquí es por que no se ingresaron las credenciales correctas
throw ValidationException::withMessages([
'email' => ['Las credenciales proporcionadas no coinciden con nuestros registros.']
]);
}
function logout(){
Auth::logout();
return redirect('login');
}
}
En el controlador de FrontController añadiremos lo siguiente:
<?php
namespace App\Http\Controllers;
use Auth;
use Inertia\Inertia;
use Illuminate\Http\Request;
class FrontController extends Controller
{
function inicio() {
return Inertia::render('Inicio', [
'user' => Auth::user()
]);
}
}
Registro de rutas
Ahora debemos de crear las rutas para estos componentes, en el archivo routes/web.php
<?php
use App\Http\Controllers\AuthController;
use App\Http\Controllers\FrontController;
use Illuminate\Support\Facades\Route;
//Rutas de registro
Route::get('/registro', [AuthController::class, 'registro']);
Route::post('/registro', [AuthController::class, 'registroPost']);
//Rutas de login
Route::get('/login', [AuthController::class, 'login'])->name('login');
Route::post('/login', [AuthController::class, 'loginPost']);
//Ruta para cerrar sesión
Route::get('/logout', [AuthController::class, 'logout'])->middleware('auth');
//Ruta de inicio
Route::get('/inicio', [FrontController::class, 'inicio'])->middleware('auth');
Correr migraciones
Para que todo funcione correctamente, necesitaremos una base de datos donde se registren nuestros usuarios. En este tutorial, utilizaremos SQLite, que viene por defecto en un nuevo proyecto de Laravel 11.
Para asegurarte de que todo funcione correctamente, te recomiendo que ejecutes el siguiente comando en la terminal:
php artisan migrate
Demostración
Para poder observar cómo funciona esto, ejecutaremos estos dos comandos en terminales separadas:
php artisan serve
npm run dev
Con esto, podremos visualizar la página en la URL http://127.0.0.1:8000/registro.
Aunque el objetivo principal de este tutorial no sea crear un formulario estético, podemos observar que al completar los campos y presionar el botón de inicio de sesión, nos redirigirá a la vista de inicio de sesión.
Si vemos nuestra base de datos veremos que se registró correctamente el usuario
En mi caso me conectare mediante HeidiSQL y si vamos a la tabla users vemos que ya hay un registro, que es el registro que acabamos de realizar.
Si volvemos a la página de inicio de sesión, podremos ingresar con el correo electrónico y la contraseña que registramos previamente.
Podremos notar que la vista cambió al componente de Inicio y podremos observar el nombre y el correo electrónico que registramos.
Si hacemos clic en el enlace de "Cerrar Sesión", seremos redirigidos a la vista de inicio de sesión. Sin embargo, si intentamos acceder nuevamente a la ruta 127.0.0.1:8000/inicio, veremos que no está permitido y seremos redirigidos nuevamente a la vista de inicio de sesión. Esto se debe a que hemos nombrado la ruta /login como "login".
Route::get('/login', [AuthController::class, 'login'])->name('login');
Control de errores
En caso de que ocurra un error en el servidor, ya sea en la vista de registro o en la vista de inicio de sesión, solo se mostrará una pequeña alerta en la parte superior de la página.
Si bien esta alerta indica que algo salió mal, no proporciona detalles sobre el problema real. Por lo tanto, realizaremos algunas modificaciones en los componentes de Inicio y de Inicio de Sesión.
El componente de Registro quedaría de la siguiente manera:
<template>
<div>
<form @submit.prevent="enviarFormulario" style="display: block">
<label for="nombre">Nombre</label>
<input type="text" v-model="nombre" name="nombre" />
<br />
<label for="correo">Correo</label>
<input type="email" v-model="correo" name="correo" />
<br />
<label for="password">Password</label>
<input type="password" v-model="password" name="password" />
<br />
<p style="color: red" v-for="(error, k) in errores" :key="k">{{ error }}</p>
<button type="submit">Registrarme</button>
</form>
</div>
</template>
<script>
import { router } from "@inertiajs/vue3";
import AuthLayout from "./../Components/AuthLayout.vue";
export default {
name: "Registro",
layout: AuthLayout,
data: () => ({
nombre: "",
correo: "",
password: "",
errores: [],
}),
methods: {
enviarFormulario() {
/* Limpiar la lista de errores antes de ejecutar la peticion */
this.errores = [];
axios
.post("/registro", {
//Se envia el formulario al servidor
nombre: this.nombre,
correo: this.correo,
password: this.password,
})
.then((res) => {
/* Si responde satisfactoriamente es por que se genero
correctamente el usuario y se le dirigira al login
a iniciar sesión */
router.get("/login");
})
.catch((err) => {
if (err.response) {
const status = err.response.status;
const data = err.response.data;
/* En caso de que exista errores en el formulario */
if (status == 422) {
let errores = [];
const keys = Object.keys(data.errors);
keys.forEach((key) => {
errores = errores.concat(data.errors[key]);
});
this.errores = errores;
} else if (status == 500) {
/* En el caso de que haya un error en el servidor */
alert("Error del servidor");
} else {
/* Cualquier otro código de error que pueda generar el servidor */
alert("Error inesperado, código: " + status);
}
} else {
/* Cualquier otro error inesperado */
alert("Error inesperado");
}
});
},
},
};
</script>
De esta manera, al presionar el botón de registro sin haber llenado ningún campo o habiendo llenado algún campo incorrectamente, se nos mostrará una lista de errores que tenemos en el formulario.
Para el componente de Login, también implementaremos una funcionalidad similar para mostrar los errores del formulario. Debería quedar así:
<template>
<div>
<form @submit.prevent="enviarFormulario">
<label for="correo">Correo</label>
<input type="email" v-model="correo" name="correo" />
<br />
<label for="password">Password</label>
<input type="password" v-model="password" name="password" />
<br />
<p style="color: red" v-for="(error, k) in errores" :key="k">{{ error }}</p>
<button type="submit">Iniciar sesión</button>
</form>
</div>
</template>
<script>
import { router } from "@inertiajs/vue3";
import AuthLayout from "./../Components/AuthLayout.vue";
export default {
name: "Login",
layout: AuthLayout,
data: () => ({
correo: "",
password: "",
errores: [],
}),
methods: {
enviarFormulario() {
axios
.post("/login", {
email: this.correo,
password: this.password,
})
.then((res) => {
router.get("/inicio");
})
.catch((err) => {
if (err.response) {
const status = err.response.status;
const data = err.response.data;
/* En caso de que exista errores en el formulario */
if (status == 422) {
let errores = [];
const keys = Object.keys(data.errors);
keys.forEach((key) => {
errores = errores.concat(data.errors[key]);
});
this.errores = errores;
} else if (status == 500) {
/* En el caso de que haya un error en el servidor */
alert("Error del servidor");
} else {
/* Cualquier otro código de error que pueda generar el servidor */
alert("Error inesperado, código: " + status);
}
} else {
/* Cualquier otro error inesperado */
alert("Error inesperado");
}
});
},
},
};
</script>
Al igual que en la vista de Registro, al hacer clic en el botón de Iniciar Sesión, también podremos observar los errores que existan en el formulario.
Conclución
Con estos avances, hemos implementado tanto el inicio de sesión como el registro en nuestra plataforma. Además, hemos desarrollado la funcionalidad para mostrar los errores presentes en el formulario, lo que facilita su identificación por parte del usuario y le permite corregirlos con mayor facilidad antes de intentarlo nuevamente.