@hemia/jwt-manager 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +502 -36
- package/dist/hemia-jwt-manager.esm.js +226 -36
- package/dist/hemia-jwt-manager.js +225 -35
- package/dist/types/index.d.ts +3 -1
- package/dist/types/interfaces/oidc-claims.d.ts +42 -0
- package/dist/types/mixin/jwt.mixin.d.ts +5 -3
- package/dist/types/services/jwt.service.d.ts +20 -3
- package/dist/types/types/token-validation.d.ts +11 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
# @hemia/jwt-manager
|
|
2
2
|
|
|
3
|
-
Una clase
|
|
3
|
+
Una clase completa y robusta para la generación, validación y decodificación de tokens JWT con soporte completo para estándares **OIDC (OpenID Connect)** y **OAuth 2.0**. Ideal para gestionar autenticación y autorización basada en tokens dentro de aplicaciones Node.js modernas.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## Características principales
|
|
7
|
+
## ✨ Características principales
|
|
8
8
|
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* ✅ **Estándares OIDC/OAuth 2.0**: Claims estándar y tokens compatibles con especificaciones oficiales
|
|
10
|
+
* 🔒 **Seguridad mejorada**: Algoritmos explícitos, validación de issuer/audience, clock skew tolerance
|
|
11
|
+
* 🎯 **Tres tipos de tokens**: ID Token, Access Token y Refresh Token
|
|
12
|
+
* 🔑 **Gestión de permisos**: Sistema de scopes para autorización granular
|
|
13
|
+
* 📝 **Claims estándar**: Soporte completo para profile, email, phone, address claims
|
|
14
|
+
* 🔄 **Retrocompatible**: Los métodos existentes siguen funcionando
|
|
15
|
+
* 🛡️ **Manejo de errores detallado**: Identificación específica de tipos de error
|
|
16
|
+
* 🚀 **TypeScript nativo**: Tipado completo y autocomplete
|
|
14
17
|
|
|
15
18
|
---
|
|
16
19
|
|
|
17
|
-
## Instalación
|
|
20
|
+
## 📦 Instalación
|
|
18
21
|
|
|
19
22
|
```bash
|
|
20
23
|
npm install @hemia/jwt-manager
|
|
@@ -22,69 +25,532 @@ npm install @hemia/jwt-manager
|
|
|
22
25
|
|
|
23
26
|
---
|
|
24
27
|
|
|
25
|
-
##
|
|
28
|
+
## 🚀 Inicio rápido
|
|
26
29
|
|
|
27
|
-
```
|
|
30
|
+
```typescript
|
|
28
31
|
import { JwtManager } from '@hemia/jwt-manager';
|
|
29
32
|
|
|
30
|
-
const
|
|
33
|
+
const jwtManager = new JwtManager();
|
|
34
|
+
|
|
35
|
+
// Crear un ID Token (OIDC)
|
|
36
|
+
const idToken = jwtManager.createIdToken({
|
|
37
|
+
sub: 'user_12345',
|
|
38
|
+
name: 'Cristian Mendez',
|
|
39
|
+
email: 'cristian@hemia.com',
|
|
40
|
+
email_verified: true
|
|
41
|
+
}, '1h');
|
|
42
|
+
|
|
43
|
+
// Crear un Access Token con permisos
|
|
44
|
+
const accessToken = jwtManager.createAccessToken(
|
|
45
|
+
'user_12345',
|
|
46
|
+
['read:products', 'write:orders'],
|
|
47
|
+
'15m'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Validar token
|
|
51
|
+
const payload = jwtManager.verify(accessToken);
|
|
52
|
+
if (payload) {
|
|
53
|
+
console.log('✅ Token válido:', payload);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Verificar permisos
|
|
57
|
+
const hasPermission = jwtManager.hasScope(accessToken, 'read:products');
|
|
58
|
+
console.log('Tiene permiso:', hasPermission);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 📖 API Reference
|
|
64
|
+
|
|
65
|
+
### 🎫 Métodos de Creación de Tokens
|
|
66
|
+
|
|
67
|
+
#### `createIdToken(claims, expiresIn?)`
|
|
68
|
+
|
|
69
|
+
Crea un **ID Token** según estándar OIDC con información de identidad del usuario.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const idToken = jwtManager.createIdToken({
|
|
73
|
+
sub: 'user_12345', // Requerido
|
|
74
|
+
name: 'Cristian Mendez',
|
|
75
|
+
email: 'cristian@hemia.com',
|
|
76
|
+
email_verified: true,
|
|
77
|
+
picture: 'https://example.com/avatar.jpg',
|
|
78
|
+
preferred_username: 'cristianm',
|
|
79
|
+
locale: 'es-MX'
|
|
80
|
+
}, '1h');
|
|
81
|
+
|
|
82
|
+
// Token contiene:
|
|
83
|
+
// {
|
|
84
|
+
// sub, name, email, email_verified, picture, preferred_username, locale,
|
|
85
|
+
// iss: 'hemia-app',
|
|
86
|
+
// aud: 'hemia-api',
|
|
87
|
+
// iat: 1703779200,
|
|
88
|
+
// exp: 1703782800
|
|
89
|
+
// }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### `createAccessToken(sub, scopes, expiresIn?)`
|
|
93
|
+
|
|
94
|
+
Crea un **Access Token** OAuth 2.0 con scopes para autorización.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
const accessToken = jwtManager.createAccessToken(
|
|
98
|
+
'user_12345',
|
|
99
|
+
['read:products', 'write:orders', 'admin:users'],
|
|
100
|
+
'15m' // Access tokens suelen ser de corta duración
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Token contiene:
|
|
104
|
+
// {
|
|
105
|
+
// sub: 'user_12345',
|
|
106
|
+
// scope: 'read:products write:orders admin:users',
|
|
107
|
+
// iss: 'hemia-app',
|
|
108
|
+
// aud: 'hemia-api',
|
|
109
|
+
// iat: 1703779200,
|
|
110
|
+
// exp: 1703780100
|
|
111
|
+
// }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### `createRefreshToken(sub, expiresIn?)`
|
|
115
|
+
|
|
116
|
+
Crea un **Refresh Token** para renovar access tokens expirados.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const refreshToken = jwtManager.createRefreshToken('user_12345', '30d');
|
|
120
|
+
|
|
121
|
+
// Token contiene:
|
|
122
|
+
// {
|
|
123
|
+
// sub: 'user_12345',
|
|
124
|
+
// type: 'refresh',
|
|
125
|
+
// jti: '1703779200-xyz123abc', // JWT ID único para revocación
|
|
126
|
+
// iss: 'hemia-app',
|
|
127
|
+
// aud: 'hemia-api',
|
|
128
|
+
// iat: 1703779200,
|
|
129
|
+
// exp: 1706371200
|
|
130
|
+
// }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `createToken(payload, expiresIn?, options?)`
|
|
134
|
+
|
|
135
|
+
Crea un JWT genérico con payload personalizado (usa clave por defecto).
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const token = jwtManager.createToken(
|
|
139
|
+
{ userId: 'abc123', role: 'admin' },
|
|
140
|
+
'2h',
|
|
141
|
+
{ issuer: 'my-app' } // Opcional
|
|
142
|
+
);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### `createTokenWithSecret(payload, secretKey, expiresIn?, options?)`
|
|
146
|
+
|
|
147
|
+
Crea un JWT genérico con una clave secreta específica.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const token = jwtManager.createTokenWithSecret(
|
|
151
|
+
{ data: 'sensitive' },
|
|
152
|
+
'custom-secret-key',
|
|
153
|
+
'1h'
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### `createCleanCredentialsToken(operative?, expiresIn?)`
|
|
158
|
+
|
|
159
|
+
Crea un token especial para operaciones sin credenciales completas.
|
|
31
160
|
|
|
32
|
-
|
|
33
|
-
const
|
|
161
|
+
```typescript
|
|
162
|
+
const token = jwtManager.createCleanCredentialsToken(Operatives.CATALOG, '2h');
|
|
34
163
|
```
|
|
35
164
|
|
|
36
165
|
---
|
|
37
166
|
|
|
38
|
-
|
|
167
|
+
### ✅ Métodos de Validación
|
|
39
168
|
|
|
40
|
-
|
|
169
|
+
#### `verify(token, secretKey?, options?)`
|
|
41
170
|
|
|
42
|
-
|
|
171
|
+
Valida un JWT y retorna el payload si es válido.
|
|
43
172
|
|
|
44
|
-
|
|
173
|
+
```typescript
|
|
174
|
+
const payload = jwtManager.verify(token);
|
|
45
175
|
|
|
46
|
-
|
|
176
|
+
if (payload) {
|
|
177
|
+
console.log('✅ Token válido');
|
|
178
|
+
console.log('Usuario:', payload.sub);
|
|
179
|
+
console.log('Email:', payload.email);
|
|
180
|
+
} else {
|
|
181
|
+
console.log('❌ Token inválido o expirado');
|
|
182
|
+
}
|
|
47
183
|
|
|
48
|
-
|
|
184
|
+
// Con secret key personalizada
|
|
185
|
+
const payload2 = jwtManager.verify(token, 'custom-secret');
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### `verifyDetailed(token, secretKey?, options?)`
|
|
189
|
+
|
|
190
|
+
Valida un JWT y retorna resultado detallado con información de error.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const result = jwtManager.verifyDetailed(token);
|
|
194
|
+
|
|
195
|
+
console.log('Válido:', result.valid);
|
|
196
|
+
|
|
197
|
+
if (result.valid) {
|
|
198
|
+
console.log('Payload:', result.payload);
|
|
199
|
+
} else {
|
|
200
|
+
console.log('Error:', result.error);
|
|
201
|
+
console.log('Tipo de error:', result.errorType);
|
|
202
|
+
// errorType: 'expired' | 'invalid' | 'not_before' | 'malformed' | 'signature_invalid'
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### 🔍 Métodos de Decodificación y Consulta
|
|
209
|
+
|
|
210
|
+
#### `decode(token, complete?)`
|
|
211
|
+
|
|
212
|
+
Decodifica un JWT **sin validarlo** (no verifica firma ni expiración).
|
|
49
213
|
|
|
50
|
-
|
|
214
|
+
```typescript
|
|
215
|
+
// Solo payload
|
|
216
|
+
const payload = jwtManager.decode(token);
|
|
51
217
|
|
|
52
|
-
|
|
218
|
+
// Con header incluido
|
|
219
|
+
const full = jwtManager.decode(token, true);
|
|
220
|
+
console.log('Header:', full.header); // { alg: 'HS256', typ: 'JWT' }
|
|
221
|
+
console.log('Payload:', full.payload);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### `getClaims(token)`
|
|
53
225
|
|
|
54
|
-
|
|
226
|
+
Extrae solo los **claims estándar OIDC** de un token.
|
|
55
227
|
|
|
56
|
-
|
|
228
|
+
```typescript
|
|
229
|
+
const claims = jwtManager.getClaims(token);
|
|
230
|
+
// Retorna solo: sub, name, email, email_verified, picture, etc.
|
|
231
|
+
// NO incluye: iss, aud, iat, exp (que son JWT claims)
|
|
232
|
+
```
|
|
57
233
|
|
|
58
|
-
|
|
234
|
+
#### `hasScope(token, requiredScope)`
|
|
59
235
|
|
|
60
|
-
|
|
236
|
+
Verifica si un token tiene un scope/permiso específico.
|
|
61
237
|
|
|
62
|
-
|
|
238
|
+
```typescript
|
|
239
|
+
const canRead = jwtManager.hasScope(token, 'read:products');
|
|
240
|
+
const canDelete = jwtManager.hasScope(token, 'delete:products');
|
|
241
|
+
|
|
242
|
+
if (canRead) {
|
|
243
|
+
console.log('✅ Usuario puede leer productos');
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### `getJwtKey()`
|
|
248
|
+
|
|
249
|
+
Obtiene la clave secreta configurada.
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
const secretKey = jwtManager.getJwtKey();
|
|
253
|
+
```
|
|
63
254
|
|
|
64
255
|
---
|
|
65
256
|
|
|
66
|
-
|
|
257
|
+
### 🔄 Métodos Legacy (Deprecated)
|
|
258
|
+
|
|
259
|
+
Estos métodos siguen funcionando para mantener compatibilidad hacia atrás:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// ⚠️ Deprecated - Usa createToken() en su lugar
|
|
263
|
+
jwtManager.getTokenWithoutKey(payload, expiresIn, options);
|
|
264
|
+
|
|
265
|
+
// ⚠️ Deprecated - Usa createTokenWithSecret() en su lugar
|
|
266
|
+
jwtManager.getTokenWithKey(payload, secretKey, expiresIn, options);
|
|
267
|
+
|
|
268
|
+
// ⚠️ Deprecated - Usa createCleanCredentialsToken() en su lugar
|
|
269
|
+
jwtManager.getTokenCleanCredentials(operative, expiresIn);
|
|
270
|
+
|
|
271
|
+
// ⚠️ Deprecated - Usa verify() en su lugar
|
|
272
|
+
jwtManager.validateToken(token, secretKey, options);
|
|
273
|
+
|
|
274
|
+
// ⚠️ Deprecated - Usa verifyDetailed() en su lugar
|
|
275
|
+
jwtManager.validateTokenDetailed(token, secretKey, options);
|
|
276
|
+
|
|
277
|
+
// ⚠️ Deprecated - Usa getClaims() en su lugar
|
|
278
|
+
jwtManager.getStandardClaims(token);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## 🔧 Configuración
|
|
284
|
+
|
|
285
|
+
### Variables de entorno
|
|
67
286
|
|
|
68
287
|
```env
|
|
69
|
-
|
|
288
|
+
# Clave secreta para firmar tokens (REQUERIDO en producción)
|
|
289
|
+
JWT_SECRET=tu_clave_super_secreta_aqui
|
|
290
|
+
|
|
291
|
+
# Emisor de los tokens (quien genera el token)
|
|
292
|
+
JWT_ISSUER=hemia-app
|
|
293
|
+
|
|
294
|
+
# Audiencia de los tokens (para quién es el token)
|
|
295
|
+
JWT_AUDIENCE=hemia-api
|
|
70
296
|
```
|
|
71
297
|
|
|
72
|
-
> ⚠️ **Importante:**
|
|
298
|
+
> ⚠️ **Importante:** Nunca uses una clave por defecto en producción. Define `JWT_SECRET` siempre en entornos seguros.
|
|
73
299
|
|
|
74
300
|
---
|
|
75
301
|
|
|
76
|
-
##
|
|
302
|
+
## 💡 Ejemplos de Uso
|
|
303
|
+
|
|
304
|
+
### Ejemplo 1: Autenticación completa
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { JwtManager } from '@hemia/jwt-manager';
|
|
308
|
+
|
|
309
|
+
const jwtManager = new JwtManager();
|
|
77
310
|
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
311
|
+
// 1. Usuario inicia sesión
|
|
312
|
+
const idToken = jwtManager.createIdToken({
|
|
313
|
+
sub: 'user_12345',
|
|
314
|
+
name: 'Cristian Mendez',
|
|
315
|
+
email: 'cristian@hemia.com',
|
|
316
|
+
email_verified: true
|
|
317
|
+
}, '1h');
|
|
318
|
+
|
|
319
|
+
const accessToken = jwtManager.createAccessToken(
|
|
320
|
+
'user_12345',
|
|
321
|
+
['read:profile', 'read:products', 'write:orders'],
|
|
322
|
+
'15m'
|
|
83
323
|
);
|
|
324
|
+
|
|
325
|
+
const refreshToken = jwtManager.createRefreshToken('user_12345', '30d');
|
|
326
|
+
|
|
327
|
+
// 2. Enviar tokens al cliente
|
|
328
|
+
res.json({ idToken, accessToken, refreshToken });
|
|
329
|
+
|
|
330
|
+
// 3. Cliente usa accessToken en requests
|
|
331
|
+
// Authorization: Bearer <accessToken>
|
|
332
|
+
|
|
333
|
+
// 4. Validar token en cada request
|
|
334
|
+
const payload = jwtManager.verify(accessToken);
|
|
335
|
+
if (!payload) {
|
|
336
|
+
return res.status(401).json({ error: 'Token inválido' });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// 5. Verificar permisos específicos
|
|
340
|
+
if (!jwtManager.hasScope(accessToken, 'write:orders')) {
|
|
341
|
+
return res.status(403).json({ error: 'Sin permisos' });
|
|
342
|
+
}
|
|
84
343
|
```
|
|
85
344
|
|
|
345
|
+
### Ejemplo 2: Middleware de autenticación (Express)
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { Request, Response, NextFunction } from 'express';
|
|
349
|
+
import { JwtManager } from '@hemia/jwt-manager';
|
|
350
|
+
|
|
351
|
+
const jwtManager = new JwtManager();
|
|
352
|
+
|
|
353
|
+
// Middleware de autenticación
|
|
354
|
+
export const authenticateToken = (req: Request, res: Response, next: NextFunction) => {
|
|
355
|
+
const authHeader = req.headers['authorization'];
|
|
356
|
+
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
|
357
|
+
|
|
358
|
+
if (!token) {
|
|
359
|
+
return res.status(401).json({ error: 'Token no proporcionado' });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const payload = jwtManager.verify(token);
|
|
363
|
+
|
|
364
|
+
if (!payload) {
|
|
365
|
+
return res.status(403).json({ error: 'Token inválido o expirado' });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
req.user = payload;
|
|
369
|
+
next();
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// Middleware de autorización por scope
|
|
373
|
+
export const requireScope = (requiredScope: string) => {
|
|
374
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
375
|
+
const authHeader = req.headers['authorization'];
|
|
376
|
+
const token = authHeader && authHeader.split(' ')[1];
|
|
377
|
+
|
|
378
|
+
if (!token || !jwtManager.hasScope(token, requiredScope)) {
|
|
379
|
+
return res.status(403).json({
|
|
380
|
+
error: `Permiso denegado. Se requiere: ${requiredScope}`
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
next();
|
|
385
|
+
};
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// Uso en rutas
|
|
389
|
+
app.get('/profile', authenticateToken, (req, res) => {
|
|
390
|
+
res.json({ user: req.user });
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
app.get('/products', authenticateToken, requireScope('read:products'), (req, res) => {
|
|
394
|
+
res.json({ products: [...] });
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
app.post('/orders', authenticateToken, requireScope('write:orders'), (req, res) => {
|
|
398
|
+
res.json({ success: true });
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Ejemplo 3: Manejo de errores detallado
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
const result = jwtManager.verifyDetailed(token);
|
|
406
|
+
|
|
407
|
+
if (!result.valid) {
|
|
408
|
+
switch (result.errorType) {
|
|
409
|
+
case 'expired':
|
|
410
|
+
// Token expirado - solicitar refresh
|
|
411
|
+
return res.status(401).json({
|
|
412
|
+
error: 'Token expirado',
|
|
413
|
+
code: 'TOKEN_EXPIRED',
|
|
414
|
+
message: 'Por favor renueva tu sesión'
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
case 'signature_invalid':
|
|
418
|
+
// Firma inválida - posible manipulación
|
|
419
|
+
console.error('⚠️ Token con firma inválida detectado');
|
|
420
|
+
return res.status(403).json({
|
|
421
|
+
error: 'Token inválido',
|
|
422
|
+
code: 'INVALID_SIGNATURE'
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
case 'not_before':
|
|
426
|
+
// Token aún no válido
|
|
427
|
+
return res.status(403).json({
|
|
428
|
+
error: 'Token no válido aún',
|
|
429
|
+
code: 'TOKEN_NOT_YET_VALID'
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
default:
|
|
433
|
+
return res.status(403).json({
|
|
434
|
+
error: 'Token inválido',
|
|
435
|
+
code: 'INVALID_TOKEN'
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Token válido
|
|
441
|
+
console.log('Usuario autenticado:', result.payload.sub);
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Ejemplo 4: Renovar tokens con Refresh Token
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
app.post('/auth/refresh', async (req, res) => {
|
|
448
|
+
const { refreshToken } = req.body;
|
|
449
|
+
|
|
450
|
+
// Validar refresh token
|
|
451
|
+
const payload = jwtManager.verify(refreshToken);
|
|
452
|
+
|
|
453
|
+
if (!payload || payload.type !== 'refresh') {
|
|
454
|
+
return res.status(401).json({ error: 'Refresh token inválido' });
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Opcional: Verificar que el jti no esté en lista negra
|
|
458
|
+
// const isRevoked = await checkIfRevoked(payload.jti);
|
|
459
|
+
// if (isRevoked) return res.status(401).json({ error: 'Token revocado' });
|
|
460
|
+
|
|
461
|
+
// Generar nuevos tokens
|
|
462
|
+
const newAccessToken = jwtManager.createAccessToken(
|
|
463
|
+
payload.sub,
|
|
464
|
+
['read:profile', 'read:products'],
|
|
465
|
+
'15m'
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
const newRefreshToken = jwtManager.createRefreshToken(payload.sub, '30d');
|
|
469
|
+
|
|
470
|
+
res.json({
|
|
471
|
+
accessToken: newAccessToken,
|
|
472
|
+
refreshToken: newRefreshToken
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## 📚 TypeScript Types
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import {
|
|
483
|
+
JwtManager,
|
|
484
|
+
StandardClaims,
|
|
485
|
+
TokenPayload,
|
|
486
|
+
TokenValidationResult,
|
|
487
|
+
TokenType
|
|
488
|
+
} from '@hemia/jwt-manager';
|
|
489
|
+
|
|
490
|
+
// StandardClaims (OIDC)
|
|
491
|
+
interface StandardClaims {
|
|
492
|
+
sub: string; // Subject (required)
|
|
493
|
+
name?: string;
|
|
494
|
+
given_name?: string;
|
|
495
|
+
family_name?: string;
|
|
496
|
+
email?: string;
|
|
497
|
+
email_verified?: boolean;
|
|
498
|
+
picture?: string;
|
|
499
|
+
// ... más claims
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// TokenValidationResult
|
|
503
|
+
interface TokenValidationResult {
|
|
504
|
+
valid: boolean;
|
|
505
|
+
payload?: any;
|
|
506
|
+
error?: string;
|
|
507
|
+
errorType?: 'expired' | 'invalid' | 'not_before' | 'malformed' | 'signature_invalid';
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// TokenType
|
|
511
|
+
enum TokenType {
|
|
512
|
+
ID_TOKEN = 'id_token',
|
|
513
|
+
ACCESS_TOKEN = 'access_token',
|
|
514
|
+
REFRESH_TOKEN = 'refresh_token'
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## 🔒 Mejoras de Seguridad Implementadas
|
|
521
|
+
|
|
522
|
+
### ✅ Algoritmo Explícito
|
|
523
|
+
Previene ataques de confusión de algoritmo especificando siempre `HS256` o `RS256`.
|
|
524
|
+
|
|
525
|
+
### ✅ Validación de Issuer y Audience
|
|
526
|
+
Verifica que el token fue emitido por quien dice serlo y para la audiencia correcta.
|
|
527
|
+
|
|
528
|
+
### ✅ Clock Skew Tolerance
|
|
529
|
+
30 segundos de tolerancia para diferencias de reloj entre servidores.
|
|
530
|
+
|
|
531
|
+
### ✅ JWT ID (jti) para Refresh Tokens
|
|
532
|
+
Permite implementar revocación de tokens mediante lista negra.
|
|
533
|
+
|
|
534
|
+
### ✅ Lista Blanca de Algoritmos
|
|
535
|
+
Solo acepta `HS256` y `RS256`, rechazando algoritmos inseguros como `none`.
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## 📖 Documentación Adicional
|
|
540
|
+
|
|
541
|
+
Para más ejemplos y documentación detallada, consulta [USAGE_EXAMPLES.md](./USAGE_EXAMPLES.md).
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## 🌐 Recursos
|
|
546
|
+
|
|
547
|
+
- [OpenID Connect Standard Claims](https://www.cerberauth.com/blog/openid-connect-standard-claims/)
|
|
548
|
+
- [RFC 7519 - JWT](https://tools.ietf.org/html/rfc7519)
|
|
549
|
+
- [OAuth 2.0 Scopes](https://oauth.net/2/scope/)
|
|
550
|
+
- [OIDC Specification](https://openid.net/specs/openid-connect-core-1_0.html)
|
|
551
|
+
|
|
86
552
|
---
|
|
87
553
|
|
|
88
|
-
## Licencia
|
|
554
|
+
## 📄 Licencia
|
|
89
555
|
|
|
90
556
|
MIT — Desarrollado por [Hemia Technologies](https://hemia.mx)
|