Propósito del proyecto:
Aprender a implementar un sistema de autenticación usando JSON Web Tokens conaccessTokende corta duración yrefreshTokenpara renovarlo automáticamente sin que el usuario tenga que iniciar sesión otra vez.
Este proyecto implementa un backend en Node.js + Express + MongoDB que autentica usuarios utilizando JWT. Es compatible tanto con aplicaciones web como móviles.
- Node.js y npm
- MongoDB local (o usar Atlas)
- Postman
- VS Code y GitHub (opcional pero útil)
- Instalación:
npm install- Crear archivo
.env:
PORT=3000
MONGO_URI=mongodb://localhost:27017/jwt-auth
JWT_SECRET=clave_segura_para_access
JWT_REFRESH_SECRET=clave_segura_para_refresh
JWT_EXPIRATION=60s
JWT_REFRESH_EXPIRATION=60s- El usuario inicia sesión.
- El backend responde con:
accessToken: válido por 60 segundosrefreshToken: válido por 60 segundos (configurable)
- El cliente guarda ambos tokens.
- Usa el
accessTokenpara acceder a rutas privadas. - Si el token expira, se recibe un error
401. - El cliente hace una petición a
/api/refreshcon elrefreshToken. - Se genera un nuevo
accessTokensin volver a loguear al usuario.
En el archivo index.js, después de configurar Express y los middlewares, se conectan las rutas de autenticación. Pero lo más importante es cómo se protegen las rutas privadas usando un middleware que verifica el accessToken.
const jwt = require('jsonwebtoken');
function verifyToken(req, res, next) {
const token = req.headers.authorization?.split(' ')[1]; // Bearer TOKEN
if (!token) return res.status(403).json({ message: 'Token requerido' });
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // Se guarda el usuario en la request
next(); // Continúa a la ruta protegida
} catch (err) {
res.status(401).json({ message: 'Token inválido o expirado' });
}
}
module.exports = verifyToken;En el archivo routes/auth.js o en cualquier otro archivo de rutas, puedes proteger una ruta así:
const verifyToken = require('../middleware/verifyToken');
router.get('/protected', verifyToken, (req, res) => {
res.json({
message: 'Acceso permitido',
usuario: req.user
});
});Esto asegura que solo usuarios con un token válido pueden acceder a esa ruta.
| Método | Ruta | Protegida | Descripción |
|---|---|---|---|
| POST | /api/register |
No | Crear un nuevo usuario |
| POST | /api/login |
No | Iniciar sesión y recibir tokens |
| GET | /api/protected |
Sí | Ruta protegida con JWT |
| POST | /api/refresh |
No | Obtener nuevo accessToken con refreshToken |
POST /api/register{
"email": "maria@correo.com",
"password": "123456"
}POST /api/login{
"email": "maria@correo.com",
"password": "123456"
}Respuesta esperada:
{
"accessToken": "...",
"refreshToken": "..."
}GET /api/protectedHeader:
Authorization: Bearer ACCESS_TOKEN
POST /api/refresh{
"refreshToken": "..."
}En el archivo .env, puedes definir cuánto tiempo duran los tokens:
JWT_EXPIRATION=60s
JWT_REFRESH_EXPIRATION=60sEstos valores son usados en el código para generar los tokens:
const accessToken = jwt.sign(
{ id: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRATION }
);
const refreshToken = jwt.sign(
{ id: user._id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: process.env.JWT_REFRESH_EXPIRATION }
);La duración puede ser:
'60s'→ 60 segundos'15m'→ 15 minutos'7d'→ 7 días- o directamente un número en segundos
- Guarda
accessTokenyrefreshTokentras login. - Usa
accessTokenen las peticiones protegidas. - Si el token expira (401), automáticamente llama a
/api/refresh. - Reemplaza el
accessTokenviejo por el nuevo. - Reintenta la petición original.
async function fetchWithAuth(url, options = {}) {
let token = localStorage.getItem("accessToken");
let res = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
if (res.status === 401) {
const refreshToken = localStorage.getItem("refreshToken");
const refreshRes = await fetch("/api/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refreshToken })
});
if (refreshRes.ok) {
const data = await refreshRes.json();
localStorage.setItem("accessToken", data.accessToken);
return fetchWithAuth(url, options);
} else {
window.location.href = "/login";
}
}
return res;
}- Usa tokens cortos (
60s) para pruebas. En producción:accessToken: 15 minrefreshToken: 7 días
- Guarda los tokens en:
- Web:
localStorageo cookies seguras - Móvil:
SharedPreferenceso almacenamiento seguro
- Web:
- El
accessTokenviaja en el header Authorization - El backend usa un middleware para verificarlo
- Las rutas privadas solo funcionan si el token es válido
- Si el token expiró, el frontend puede usar el
refreshTokenpara obtener uno nuevo automáticamente