Package Exports
- @smertins27/jwt-auth-manager
Readme
@smertins27/jwt-auth-manager
Modernes JWT-Management-Package mit Access & Refresh Token Rotation für Node.js und Express.
Features
- Access & Refresh Token Generation mit automatischer Rotation
- jose statt jsonwebtoken - Moderne Library für 2025 mit Web Crypto API
- Refresh Token Rotation mit Reuse Detection zum Schutz vor Token-Diebstahl
- Express Middleware für automatische Zugriffssteuerung
- Token Blacklisting für sofortige Revokation (Logout, Sicherheitsvorfälle)
- TypeScript mit vollständigen Typdefinitionen
- Konfigurierbare Excluded Endpoints (Login, Public Routes)
- ESM und CommonJS Support
- Node.js 18+ kompatibel (getestet mit Node 22 LTS)
- Sichere Defaults (HS256, kurze Access Token Lifetime)
Sicherheits-Features
- Kurze Access Token Lebensdauer (Default: 15 Minuten)
- Lange Refresh Token Lebensdauer (Default: 7 Tage)
- Automatische Token Rotation bei jedem Refresh
- Reuse Detection erkennt kompromittierte Refresh Tokens
- Algorithm Enforcement - "none" Algorithm wird blockiert
- Token Expiry Validation mit jose
- CORS Header Support
- Flexible Blacklist-Integration (In-Memory, Redis, DB)
Installation
npm install @smertins27/jwt-auth-manager josePeer Dependency:
npm install expressSchnellstart
1. Basic Token Generation
import { JwtTokenManager } from '@smertins27/jwt-auth-manager';
const manager = new JwtTokenManager({
secret: process.env.JWT_SECRET!,
accessTokenExpiry: '15m',
refreshTokenExpiry: '7d',
});
// Token-Pair generieren (z.B. nach Login)
const tokens = await manager.generateTokenPair({
uid: 'user123',
email: 'user@example.com',
role: 'admin',
});
console.log('Access Token:', tokens.accessToken);
console.log('Refresh Token:', tokens.refreshToken);
console.log('Expires in:', tokens.expiresIn, 'seconds');2. Token Verification
// Access Token verifizieren
try {
const { payload } = await manager.verifyAccessToken(accessToken);
console.log('User ID:', payload.uid);
console.log('Role:', payload.role);
} catch (error) {
console.error('Invalid token:', error.message);
}3. Express Middleware Setup
import express from 'express';
import { JwtTokenManager, createAuthMiddleware } from '@smertins27/jwt-auth-manager';
const app = express();
const manager = new JwtTokenManager({ secret: process.env.JWT_SECRET! });
// Middleware mit Excluded Endpoints
const authMiddleware = createAuthMiddleware(
{
secret: process.env.JWT_SECRET!,
excludedEndpoints: [
{ endpoint: '/api/auth/login', methods: ['POST'] },
{ endpoint: '/api/auth/register', methods: ['POST'] },
{ endpoint: '/api/health', methods: ['GET'] },
{ endpoint: new RegExp('^/api/public/.*'), methods: ['GET'] },
],
},
manager
);
// Middleware global anwenden
app.use(authMiddleware);
// Geschützte Route
app.get('/api/profile', (req, res) => {
// req.user ist jetzt verfügbar
res.json({ user: req.user });
});
app.listen(3000);Verwendung
Komplettes Login-Flow-Beispiel
import express from 'express';
import {
JwtTokenManager,
RefreshTokenManager,
MemoryRefreshTokenStore,
createAuthMiddleware,
} from '@smertins27/jwt-auth-manager';
const app = express();
app.use(express.json());
// Manager Setup
const tokenManager = new JwtTokenManager({
secret: process.env.JWT_SECRET!,
accessTokenExpiry: '15m',
refreshTokenExpiry: '7d',
issuer: 'my-app',
audience: 'my-app-users',
});
const refreshStore = new MemoryRefreshTokenStore();
const refreshManager = new RefreshTokenManager(tokenManager, refreshStore);
// Auth Middleware
const authMiddleware = createAuthMiddleware(
{
secret: process.env.JWT_SECRET!,
excludedEndpoints: [
{ endpoint: '/auth/login', methods: ['POST'] },
{ endpoint: '/auth/refresh', methods: ['POST'] },
],
},
tokenManager
);
app.use(authMiddleware);
// Login Route
app.post('/auth/login', async (req, res) => {
const { username, password } = req.body;
// Hier: Benutzer gegen LDAP/DB validieren
// z.B. mit @smertins27/ldap-auth-wrapper
const user = { uid: 'user123', username, role: 'admin' };
// Token-Pair generieren mit Refresh Token Store
const tokens = await refreshManager.createTokenPair({
uid: user.uid,
username: user.username,
role: user.role,
});
res.json(tokens);
});
// Refresh Route
app.post('/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
try {
// Token rotieren (alter wird invalidiert, neuer generiert)
const newTokens = await refreshManager.rotateRefreshToken(refreshToken);
res.json(newTokens);
} catch (error: any) {
if (error.code === 'TOKEN_REUSE_DETECTED') {
// SECURITY: Alle Tokens des Users wurden revoked!
return res.status(401).json({ error: 'Token reuse detected - please login again' });
}
res.status(401).json({ error: error.message });
}
});
// Logout Route
app.post('/auth/logout', async (req, res) => {
const userId = req.user!.uid;
// Alle Refresh Tokens des Users invalidieren
await refreshManager.revokeAllTokens(userId);
res.json({ message: 'Logged out successfully' });
});
// Geschützte Route
app.get('/api/profile', (req, res) => {
res.json({
user: req.user,
token: req.token, // Original Access Token
});
});
app.listen(3000);Mit Token Blacklisting (Logout Support)
import {
JwtTokenManager,
createAuthMiddleware,
MemoryTokenBlacklist,
} from '@smertins27/jwt-auth-manager';
const blacklist = new MemoryTokenBlacklist();
const tokenManager = new JwtTokenManager({
secret: process.env.JWT_SECRET!,
});
// Middleware mit Blacklist
const authMiddleware = createAuthMiddleware(
{
secret: process.env.JWT_SECRET!,
isBlacklisted: async (token, payload) => {
return await blacklist.isBlacklisted(token);
},
},
tokenManager
);
app.use(authMiddleware);
// Logout: Access Token zur Blacklist hinzufügen
app.post('/auth/logout', async (req, res) => {
const token = req.token!;
const payload = req.user!;
// Berechne verbleibende Gültigkeit
const decoded = tokenManager.decodeToken(token);
const expirySeconds = (decoded.exp || 0) - Math.floor(Date.now() / 1000);
// Zur Blacklist hinzufügen (nur bis Token abläuft)
await blacklist.add(token, expirySeconds);
res.json({ message: 'Logged out' });
});Custom Token Extractor
const authMiddleware = createAuthMiddleware(
{
secret: process.env.JWT_SECRET!,
// Custom Token Extractor (z.B. aus Cookie)
tokenExtractor: (req) => {
return req.cookies?.accessToken;
},
},
tokenManager
);Mit unterschiedlichen Algorithmen
// HMAC (Symmetric)
const hmacManager = new JwtTokenManager({
secret: 'your-secret-key',
algorithm: 'HS256', // oder HS384, HS512
});
// RSA (Asymmetric) - benötigt Private Key
import { readFileSync } from 'fs';
const privateKey = readFileSync('./private-key.pem');
const rsaManager = new JwtTokenManager({
secret: privateKey,
algorithm: 'RS256', // oder RS384, RS512
});
// EdDSA (Modern, empfohlen für neue Projekte)
const eddsaManager = new JwtTokenManager({
secret: privateKey, // EdDSA Private Key
algorithm: 'EdDSA',
});TypeScript Integration mit Custom Payload
import { TokenPayload } from '@smertins27/jwt-auth-manager';
// Custom Payload Type
interface MyTokenPayload extends TokenPayload {
uid: string;
email: string;
role: 'admin' | 'user' | 'guest';
permissions: string[];
}
// Type-safe Token Generation
const tokens = await manager.generateTokenPair({
uid: 'user123',
email: 'user@example.com',
role: 'admin',
permissions: ['read', 'write', 'delete'],
} as MyTokenPayload);
// Type-safe Verification
const { payload } = await manager.verifyAccessToken(accessToken);
const typedPayload = payload as MyTokenPayload;
console.log(typedPayload.permissions); // ✅ TypeScript wei?, dass es ein string[] istNestJS Integration
import { Injectable, UnauthorizedException } from '@nestjs/common';
import {
JwtTokenManager,
RefreshTokenManager,
MemoryRefreshTokenStore,
TokenPair,
} from '@smertins27/jwt-auth-manager';
@Injectable()
export class AuthService {
private tokenManager: JwtTokenManager;
private refreshManager: RefreshTokenManager;
constructor() {
this.tokenManager = new JwtTokenManager({
secret: process.env.JWT_SECRET!,
accessTokenExpiry: '15m',
refreshTokenExpiry: '7d',
});
const refreshStore = new MemoryRefreshTokenStore();
this.refreshManager = new RefreshTokenManager(this.tokenManager, refreshStore);
}
async login(userId: string, userData: any): Promise<TokenPair> {
return await this.refreshManager.createTokenPair({
uid: userId,
...userData,
});
}
async refresh(refreshToken: string): Promise<TokenPair> {
try {
return await this.refreshManager.rotateRefreshToken(refreshToken);
} catch (error: any) {
throw new UnauthorizedException(error.message);
}
}
async logout(userId: string): Promise<void> {
await this.refreshManager.revokeAllTokens(userId);
}
}NestJS Guard
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { JwtTokenManager } from '@smertins27/jwt-auth-manager';
@Injectable()
export class JwtAuthGuard implements CanActivate {
private tokenManager: JwtTokenManager;
constructor() {
this.tokenManager = new JwtTokenManager({
secret: process.env.JWT_SECRET!,
});
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const authHeader = request.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return false;
}
const token = authHeader.slice(7);
try {
const { payload } = await this.tokenManager.verifyAccessToken(token);
request.user = payload;
return true;
} catch {
return false;
}
}
}Redis Blacklist (Production-Ready)
import { TokenBlacklist } from '@smertins27/jwt-auth-manager';
import Redis from 'ioredis';
export class RedisTokenBlacklist implements TokenBlacklist {
private redis: Redis;
constructor(redisUrl: string) {
this.redis = new Redis(redisUrl);
}
async add(token: string, expirySeconds: number): Promise<void> {
await this.redis.setex(`blacklist:${token}`, expirySeconds, '1');
}
async isBlacklisted(token: string): Promise<boolean> {
const result = await this.redis.get(`blacklist:${token}`);
return result === '1';
}
async cleanup(): Promise<void> {
// Redis TTL handled automatically
}
}
// Usage
const blacklist = new RedisTokenBlacklist(process.env.REDIS_URL!);PostgreSQL Refresh Token Store
import { RefreshTokenStore } from '@smertins27/jwt-auth-manager';
import { Pool } from 'pg';
export class PostgresRefreshTokenStore implements RefreshTokenStore {
private pool: Pool;
constructor(connectionString: string) {
this.pool = new Pool({ connectionString });
}
async save(jti: string, userId: string, expiryDate: Date): Promise<void> {
await this.pool.query(
'INSERT INTO refresh_tokens (jti, user_id, expires_at) VALUES ($1, $2, $3)',
[jti, userId, expiryDate]
);
}
async exists(jti: string): Promise<boolean> {
const result = await this.pool.query(
'SELECT 1 FROM refresh_tokens WHERE jti = $1 AND expires_at > NOW()',
[jti]
);
return result.rowCount > 0;
}
async invalidate(jti: string): Promise<void> {
await this.pool.query('DELETE FROM refresh_tokens WHERE jti = $1', [jti]);
}
async invalidateAllForUser(userId: string): Promise<void> {
await this.pool.query('DELETE FROM refresh_tokens WHERE user_id = $1', [userId]);
}
}
// Database Schema
/*
CREATE TABLE refresh_tokens (
jti VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id);
CREATE INDEX idx_refresh_tokens_expires_at ON refresh_tokens(expires_at);
*/Environment Variables Best Practice
import { JwtTokenManager } from '@smertins27/jwt-auth-manager';
const tokenManager = new JwtTokenManager({
secret: process.env.JWT_SECRET!,
algorithm: (process.env.JWT_ALGORITHM as any) || 'HS256',
accessTokenExpiry: process.env.JWT_ACCESS_EXPIRY || '15m',
refreshTokenExpiry: process.env.JWT_REFRESH_EXPIRY || '7d',
issuer: process.env.JWT_ISSUER || 'my-app',
audience: process.env.JWT_AUDIENCE || 'my-app-users',
});.env Beispiel:
JWT_SECRET=your-super-secret-key-min-32-chars
JWT_ALGORITHM=HS256
JWT_ACCESS_EXPIRY=15m
JWT_REFRESH_EXPIRY=7d
JWT_ISSUER=my-app
JWT_AUDIENCE=my-app-usersAPI
JwtTokenManager
Hauptklasse für Token-Generation und -Verification.
Constructor
new JwtTokenManager(config: JwtConfig)JwtConfig:
interface JwtConfig {
secret: string | Uint8Array;
algorithm?: 'HS256' | 'HS384' | 'HS512' | 'RS256' | 'RS384' | 'RS512' | 'ES256' | 'ES384' | 'ES512' | 'EdDSA';
accessTokenExpiry?: string; // Default: '15m'
refreshTokenExpiry?: string; // Default: '7d'
issuer?: string;
audience?: string;
}Methoden
generateTokenPair(payload: TokenPayload): Promise<TokenPair>
Generiert Access + Refresh Token Pair.
Returns:
interface TokenPair {
accessToken: string;
refreshToken: string;
expiresIn: number; // Seconds
}generateAccessToken(payload: TokenPayload): Promise<string>
Generiert nur einen Access Token.
verifyAccessToken(token: string): Promise<VerifiedToken<TokenPayload>>
Verifiziert Access Token und gibt Payload zurück.
verifyRefreshToken(token: string): Promise<VerifiedToken<RefreshTokenPayload>>
Verifiziert Refresh Token und gibt Payload zurück.
decodeToken(token: string): JWTPayload
Dekodiert Token ohne Verification (nur für Debugging!).
RefreshTokenManager
Manager für Refresh Token Rotation.
Constructor
new RefreshTokenManager(tokenManager: JwtTokenManager, tokenStore: RefreshTokenStore)Methoden
createTokenPair(payload: TokenPayload): Promise<TokenPair>
Erstellt Token Pair und speichert Refresh Token im Store.
rotateRefreshToken(refreshToken: string): Promise<TokenPair>
Rotiert Refresh Token (alter wird invalidiert, neuer generiert).
Wirft Fehler bei Token Reuse Detection!
revokeAllTokens(userId: string): Promise<void>
Invalidiert alle Refresh Tokens eines Users.
createAuthMiddleware(config: MiddlewareConfig, tokenManager: JwtTokenManager)
Erstellt Express Middleware für automatische Token-Verification.
MiddlewareConfig:
interface MiddlewareConfig {
secret: string | Uint8Array;
algorithm?: JwtConfig['algorithm'];
excludedEndpoints?: ExcludedEndpoint[];
tokenExtractor?: (req: Request) => string | undefined;
isBlacklisted?: (token: string, payload: TokenPayload) => Promise<boolean>;
corsOrigin?: string; // Default: '*'
}
interface ExcludedEndpoint {
endpoint: string | RegExp;
methods: HttpMethod[];
}Erweitert Request Objekt:
interface AuthRequest extends Request {
user?: TokenPayload;
token?: string;
}Interfaces
TokenBlacklist
Interface für Blacklist-Implementierung.
interface TokenBlacklist {
add(token: string, expirySeconds: number): Promise<void>;
isBlacklisted(token: string): Promise<boolean>;
cleanup?(): Promise<void>;
}Provided Implementations:
MemoryTokenBlacklist- In-Memory (Development)
RefreshTokenStore
Interface für Refresh Token Persistence.
interface RefreshTokenStore {
save(jti: string, userId: string, expiryDate: Date): Promise<void>;
exists(jti: string): Promise<boolean>;
invalidate(jti: string): Promise<void>;
invalidateAllForUser(userId: string): Promise<void>;
}Provided Implementations:
MemoryRefreshTokenStore- In-Memory (Development)
Sicherheits-Best-Practices
1. Secret Key Sicherheit
// FALSCH - Hardcoded Secret
const manager = new JwtTokenManager({ secret: 'mysecret' });
// RICHTIG - Environment Variable mit mind. 32 Zeichen
const manager = new JwtTokenManager({
secret: process.env.JWT_SECRET!, // min. 32 chars!
});Secret generieren:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"2. Token Lifetimes
// ✅ Empfohlene Werte
const manager = new JwtTokenManager({
secret: process.env.JWT_SECRET!,
accessTokenExpiry: '15m', // Max 30 Min
refreshTokenExpiry: '7d', // Max 14 Tage
});3. HTTPS Only
Access Tokens sollten nur über HTTPS übertragen werden!
// Express HTTPS Enforcement
app.use((req, res, next) => {
if (process.env.NODE_ENV === 'production' && !req.secure) {
return res.redirect('https://' + req.headers.host + req.url);
}
next();
});4. Token Storage (Client-Side)
Empfehlung:
- Access Token: Memory (JavaScript Variable)
- Refresh Token: HttpOnly Cookie oder Secure Storage
// Server-side: Refresh Token als HttpOnly Cookie
app.post('/auth/login', async (req, res) => {
const tokens = await refreshManager.createTokenPair(payload);
res.cookie('refreshToken', tokens.refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 Tage
});
res.json({ accessToken: tokens.accessToken });
});5. Refresh Token Rotation
Immer Refresh Token Rotation verwenden!
// ✅ RICHTIG - Mit Rotation
const newTokens = await refreshManager.rotateRefreshToken(oldToken);
// ⌠FALSCH - Kein Rotation
const newAccess = await tokenManager.generateAccessToken(payload);6. Algorithm Enforcement
// ✅ RICHTIG - Algorithm spezifiziert
const manager = new JwtTokenManager({
secret: process.env.JWT_SECRET!,
algorithm: 'HS256', // Explizit!
});
// Das Package blockiert automatisch "none" AlgorithmTroubleshooting
Problem: "Invalid token signature"
Ursache: Secret Key stimmt nicht überein
Lösung:
- Überprüfe
JWT_SECRETEnvironment Variable - Stelle sicher, dass derselbe Secret für Sign und Verify verwendet wird
Problem: "Token has expired"
Ursache: Access Token ist abgelaufen
Lösung:
- Verwende Refresh Token um neues Token-Pair zu holen
- Implementiere automatisches Token Refresh im Client
// Client-side Auto-Refresh
async function fetchWithAuth(url: string, options: RequestInit = {}) {
let token = getAccessToken();
const response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
// Wenn 401, versuche Refresh
if (response.status === 401) {
const newTokens = await refreshAccessToken();
setAccessToken(newTokens.accessToken);
// Retry mit neuem Token
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${newTokens.accessToken}`,
},
});
}
return response;
}Problem: "Token reuse detected"
Ursache: Refresh Token wurde mehrfach verwendet (möglicher Angriff!)
Lösung:
- User muss sich neu einloggen
- Alle Tokens wurden aus Sicherheitsgründen revoked
Problem: TypeScript Errors mit req.user
Ursache: Express Request Type nicht erweitert
Lösung:
// types/express.d.ts
import { TokenPayload } from '@smertins27/jwt-auth-manager';
declare global {
namespace Express {
interface Request {
user?: TokenPayload;
token?: string;
}
}
}
export {};Migration von jsonwebtoken
Falls du von jsonwebtoken migrierst:
Vorher (jsonwebtoken)
import jwt from 'jsonwebtoken';
const token = jwt.sign({ uid: 'user123' }, 'secret', { expiresIn: '15m' });
const payload = jwt.verify(token, 'secret');Nachher (jwt-auth-manager)
import { JwtTokenManager } from '@smertins27/jwt-auth-manager';
const manager = new JwtTokenManager({
secret: 'secret',
accessTokenExpiry: '15m',
});
const token = await manager.generateAccessToken({ uid: 'user123' });
const { payload } = await manager.verifyAccessToken(token);Vorteile:
- Async/await statt Callbacks
- Web Crypto API (schneller)
- Bessere TypeScript-Unterstützung
- ESM-first
- Eingebaute Refresh Token Rotation
- Middleware inklusive
Kompatibilität
- Node.js: >= 18.0.0 (getestet mit Node 22 LTS)
- TypeScript: >= 5.0.0
- Express: ^4.18.0 || ^5.0.0
Abhängigkeiten
jose: ^5.9.0
Peer Dependencies
express: ^4.18.0 || ^5.0.0
Development
# Repository klonen
git clone https://github.com/dein-username/jwt-auth-manager.git
cd jwt-auth-manager
# Dependencies installieren
npm install
# Build
npm run build
# Tests
npm test
# Watch-Modus
npm run devContributing
Contributions sind willkommen! Bitte:
- Fork das Repository
- Erstelle einen Feature-Branch (
git checkout -b feature/AmazingFeature) - Commit deine Änderungen (
git commit -m 'Add AmazingFeature') - Push zum Branch (
git push origin feature/AmazingFeature) - Öffne einen Pull Request
Lizenz
MIT License - siehe LICENSE Datei für Details.
Support
Bei Fragen oder Problemen:
- Öffne ein Issue
- Siehe Discussions
Changelog
1.0.0 (2025-10-30)
- Initial Release
- JWT Token Generation & Verification mit jose
- Refresh Token Rotation mit Reuse Detection
- Express Middleware
- Token Blacklisting Support
- In-Memory Stores für Development
- TypeScript Definitionen
- ESM + CommonJS Support
- Vollständige Dokumentation
Verwandte Packages
- @smertins27/ldap-auth-wrapper - LDAP Authentication für Login-Flow
Danksagungen
Basiert auf jose - dem modernen JWT-Standard für JavaScript.
Sicherheitshinweis: Dieses Package implementiert moderne JWT Best Practices für 2025. Bitte verwende in Produktion:
- Sichere Secret Keys (min. 32 Zeichen)
- HTTPS-only Token-Übertragung
- Kurze Access Token Lifetimes
- Persistente Refresh Token Stores (Redis/DB)
- Persistente Blacklists (Redis/DB)
- Regelmä?ige Security-Audits