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.