← Blog

Autenticación JWT con Node.js y React: implementación completa paso a paso

D
Damián Oliva
·3 de marzo de 2025

Por qué JWT para autenticación web

JWT (JSON Web Token) es el estándar de facto para autenticación en APIs REST. A diferencia de las sesiones tradicionales basadas en cookies y almacenamiento en servidor, los JWT son stateless: toda la información necesaria está en el token mismo, firmado con una clave secreta.

Ventaja principal: escala horizontalmente sin necesidad de almacenamiento compartido de sesiones entre servidores.

Backend: generar y verificar tokens con Node.js

Instalación:

npm install jsonwebtoken bcryptjs

Ruta de login:

import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
import User from '../models/User.js';

export const login = async (req, res) => {
  const { email, password } = req.body;

  const user = await User.findOne({ email });
  if (!user) return res.status(401).json({ error: 'Credenciales inválidas' });

  const isMatch = await bcrypt.compare(password, user.password);
  if (!isMatch) return res.status(401).json({ error: 'Credenciales inválidas' });

  const token = jwt.sign(
    { id: user._id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  );

  res.json({ token, user: { id: user._id, name: user.name, email: user.email } });
};

Middleware de autenticación

export const auth = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: 'No autorizado' });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch {
    res.status(401).json({ error: 'Token inválido' });
  }
};

// Middleware de roles
export const requireRole = (role) => (req, res, next) => {
  if (req.user.role !== role) {
    return res.status(403).json({ error: 'Sin permisos' });
  }
  next();
};

Frontend: AuthContext con React

import { createContext, useContext, useState, useEffect } from 'react';
import api from '../utils/api';

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const token = localStorage.getItem('token');
    if (token) {
      api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
      // Opcional: verificar token con el backend
    }
    setLoading(false);
  }, []);

  const login = async (email, password) => {
    const { data } = await api.post('/auth/login', { email, password });
    localStorage.setItem('token', data.token);
    api.defaults.headers.common['Authorization'] = `Bearer ${data.token}`;
    setUser(data.user);
  };

  const logout = () => {
    localStorage.removeItem('token');
    delete api.defaults.headers.common['Authorization'];
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, login, logout, loading }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => useContext(AuthContext);

Proteger rutas en React Router

function ProtectedRoute({ children }) {
  const { user, loading } = useAuth();
  if (loading) return <Spinner />;
  if (!user) return <Navigate to="/login" />;
  return children;
}

Buenas prácticas de seguridad

  • Nunca guardes el JWT en localStorage si el contenido es muy sensible; considerá httpOnly cookies.
  • Usá tiempos de expiración cortos (1h) con refresh tokens para sesiones largas.
  • Siempre verificá el token en el backend, nunca confíes solo en el frontend.
  • Rotá el JWT_SECRET periódicamente y tené un mecanismo de revocación para tokens comprometidos.
Newsletter

Ideas directamente
a tu bandeja de entrada

Comparto lo que estoy construyendo, lo que aprendo y lo que me parece interesante del mundo tech y startups. Sin spam. Cuando tenga algo que valga la pena.

Sin spam. Unsubscribe en cualquier momento.