Login en Vue.js contra servidor node.js

Después de unas semanas sin poder escribir nada en el blog, vuelvo a la carga con una actualización del último post.

Si en la última entrada vimos como montar una api y un login con autenticación JWT con node.js, en esta ocasión vamos a ver la aplicación práctica de ese login. Vamos a loguearnos en una aplicación desde Vue.js.

Contexto

La funcionalidad de login que vamos a ver, forma parte de un proyecto más amplio que estoy llevando a cabo. Una pequeña aplicación web que permite al usuario cargar imágenes y crear una galeria de fotos.

Como se puede ver, la aplicación tiene un menú de login a la derecha, que permite mostrar un formulario para que el usuario se puede validar y acceder a las opciones de cargar imagen y desconectarse cuando sea necesario.

El código

Teniendo en cuenta lo anterior, vamos a revisar el código. Tomando como ejemplo de partida el post anterior, creamos un servidor node donde configuramos el servicio de login:

// server.js
const express = require('express');
// Inicializa una instancia de la aplicación express
const app = express();
// Define el puerto en el que se ejecutará el servidor, tomando el valor del entorno si está definido, de lo contrario utiliza el puerto 3000
const PORT = process.env.PORT || 3000;
// Importa la configuración de la conexión a la base de datos
const connection = require('./config/config_DB');
// Importa funciones y constantes relacionadas con la autenticación JWT
const { authenticateToken, jwt, secretKey } = require('./config/config_JWT');


// Middleware para habilitar CORS
app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "http://localhost:8080"); 
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});

// ......
// OTRAS OPCIONES AQUI
// ......

// Endpoint para autenticar un usuario y generar un token JWT
app.post('/login', (req, res) => {
    // Obtiene el nombre de usuario y la contraseña del cuerpo de la solicitud
    const { username, password } = req.body;
    // Verifica si el usuario existe en la base de datos y si la contraseña coincide
    connection.query('SELECT * FROM app_usuarios WHERE nombre_usuario = ? AND contraseña = ?', [username, password], (error, results) => {
        if (error) {
            console.error('Error al buscar usuario:', error);
            return res.status(500).send('Error interno del servidor');
        }
        if (results.length === 0) {
            // Si no se encuentra el usuario o la contraseña es incorrecta, envía una respuesta de credenciales incorrectas
            return res.status(401).json({ message: 'Credenciales incorrectas' });
        }
        // Si la autenticación es exitosa, genera un token JWT y lo envía al cliente como respuesta
        const user = results[0]; // Obtiene el primer resultado (debería haber solo uno)
        const tokenPayload = {
            username: user.nombre_usuario,
            fullName: user.nombre_completo
        };
        const token = jwt.sign(tokenPayload, secretKey);

        res.json({ token: token, fullName: user.nombre_completo });
    });
});

// Inicia el servidor y lo hace escuchar en el puerto especificado
app.listen(PORT, () => {
    console.log(`Servidor escuchando en el puerto ${PORT}`);
});

Una vez definido el endpoint vamos a hacer las configuraciones necesarias en nuestra aplicación del frontend con Vue.js.

Como siempre, me gusta trabajar con un entorno más o menos ordenado y coloco la lógica de accesos a las diferentes API’s en un fichero llamado axios_instance.js dentro de /src/logic.

Esto me sirve a mi para tener la aplicación organizada, pero no es ninguna convención, solo una decisión personal.

En el fichero axios_instance.js, coloco las llamadas al endpoint y también la gestión del login.

// axios_instance.js
import axios from 'axios';

// Creamos una instancia de axios
const axiosInstance = axios.create({
    baseURL: 'http://localhost:3000/', // EndPoint de pruebas
    timeout: 5000, // Tiempo máximo de espera para la petición
});

// Función para realizar la solicitud de inicio de sesión
const login = async (username, password) => {

    try {
        const response = await axiosInstance.post('/login', {
            username: username,
            password: password
        });
        return response.data; // Retorna los datos de la respuesta (en este caso, el token JWT)

    } catch (error) {
        throw error; // Lanza cualquier error que ocurra durante la solicitud
    }
};

// ...
// OTRAS FUNCIONES RELACIONADAS CON LAS APIS
// ...
export  {axiosInstance, login, // OTRAS FUNCIONES A EXPORTAR } ;

Ahora ya podemos importar la llamada al login desde una de las vistas de nuestra aplicación Vue.js donde queramos que esté disponible. En mi caso he querido hacerlo desde App.vue, para que esté disponible en toda la aplicación.

Para ello, he montado un pequeño modal con el formulario de login, para que se desplegue por encima de la aplicación, como se ha podido ver en las imágenes del inicio del post.

 <!-- Modal de inicio de sesión -->
  <div class="modal fade" id="loginModal" tabindex="-1" aria-labelledby="loginModalLabel" aria-hidden="true">
    <div class="modal-dialog" style="width: 20vw;">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="loginModalLabel">Inicio de Sesión</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <form @submit.prevent="handleSubmit">
            <!-- Contenido del formulario de inicio de sesión -->
            <div class="mb-3">
              <label for="username" class="form-label">Usuario</label>
              <input type="text" class="form-control" id="username" v-model="username" required>
            </div>
            <div class="mb-3">
              <label for="password" class="form-label">Contraseña</label>
              <input type="password" class="form-control" id="password" v-model="password" required>
            </div>
           </form>
        </div>
      </div>
    </div>
  </div>

La gestión del login pasa por:

<script>
import { ref, onMounted } from 'vue';
import { login, uploadImage } from '@/logic/axios_instance.js';
import { useTokenStore } from '@/store/useTokenStore';

export default {
  setup() {
    const username = ref('');
    const password = ref('');
    let loginModal = null;
    // Otras definiciones
    // .....
    // Gestionamos el token si existe
    const { setToken, clearSessionStorage, isConnected } = useTokenStore();

// Manejamos el proceso de login
  const handleSubmit = async () => {
      try {
        if (isConnected()) { // Aquí llamamos a la función isConnected()
          clearSessionStorage();
          alert("Logout correcto!");
        } else {
          const response = await login(username.value, password.value);
          if (response && response.token) {
            sessionStorage.setItem('authToken', response.token);
            sessionStorage.setItem('fullName', response.fullName);
            setToken(response.token);
            alert("Login correcto!");
            loginModal.hide();
          } else {
            throw new Error("No se recibió un token válido.");
          }
        }
      } catch (error) {
        alert("Error al iniciar sesión: " + error.message);
      }
    };

    const handleLogout = () => {
      clearSessionStorage();
      setToken(null); // Actualiza el token a null, lo que también actualiza isConnected
      alert("Logout correcto!");
    };

  // ...
  // Otras funciones o métodos
  // ...
}

En resumen, la aplicación recoge el usuario y la contraseña, la compara con la base de datos a través de la API y si está correcta la validación genera un token JWT que guarda en un store (que veremos en otra ocasión como montarlo) y lo almacena en SessionStorage en el navegador, durante el resto de navegación del usuario, para poder ejecutar otras acciones que requieran de autenticación.

El proceso de logout es muy simple. Sencillamente borra el SessionStorage y refresca la aplicación, para que el token no esté disponible.

Hasta aquí lo que os queria contar sobre el login. Veremos en otra ocasión, como decía antes, como usar stores para almacenar estados de la aplicación y poder usarlos en otras partes.

No dejo de momento el código disponible en mi repositorio de GitHub porque está todavía la aplicación en desarrollo y como os decía, el login es solo una funcionalidad más de la misma.

Pero os invito a visitar el resto de código que tengo publicado.

Por Jose Manuel Sanz Prieto

Desarrollador web. En este blog hablo de fotografía, programación con Django, Python, PHP y privacidad.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *