Skip to content

Latest commit

 

History

History
702 lines (562 loc) · 17.8 KB

File metadata and controls

702 lines (562 loc) · 17.8 KB

Authentication & Security

Comprehensive guide to authentication, authorization, and security features in the Qwe Framework.

Table of Contents

Overview

Qwe Framework provides a complete authentication and authorization system built on JWT (JSON Web Tokens) with zero external dependencies. The system includes token generation, validation, middleware for route protection, and flexible authorization patterns.

Key Features

  • 🔐 JWT Implementation: Complete JWT library with HS256/384/512 support
  • 🛡️ Middleware System: Easy route protection with configurable options
  • 👥 Role-Based Access: Built-in role and permission-based authorization
  • 🔑 Flexible Token Extraction: Multiple token sources (headers, query, cookies)
  • ⚡ High Performance: Zero dependencies, optimized for speed
  • 🔒 Security First: Built-in protections against common vulnerabilities

JWT Authentication

Core JWT Operations

The framework provides a complete JWT implementation accessible through the context object:

// Using JWT through context
app.post('/auth/login', async (qwe) => {
  const { email, password } = qwe.body;
  
  // Authenticate user (implement your logic)
  const user = await authenticateUser(email, password);
  if (!user) {
    return qwe.unauthorized('Invalid credentials');
  }
  
  // Generate JWT token
  const token = qwe.jwt.sign(
    { 
      userId: user.id, 
      email: user.email,
      role: user.role,
      permissions: user.permissions 
    },
    process.env.JWT_SECRET!,
    { 
      expiresIn: '24h',
      issuer: 'my-app',
      audience: 'my-api'
    }
  );
  
  return qwe.success('Login successful', { 
    token, 
    user: { id: user.id, email: user.email, role: user.role }
  });
});

Standalone JWT Usage

import { createJWT } from 'qwe-framework';

const jwt = createJWT();

// Sign a token
const token = jwt.sign(
  { userId: 123, role: 'admin' },
  'your-secret-key',
  {
    expiresIn: '1h',
    issuer: 'my-app',
    audience: 'my-api',
    subject: 'user-auth'
  }
);

// Verify a token
try {
  const payload = jwt.verify(token, 'your-secret-key');
  console.log('User:', payload.userId, 'Role:', payload.role);
} catch (error) {
  console.error('Token verification failed:', error.message);
}

// Decode token without verification (for debugging)
const decoded = jwt.decode(token);
console.log('Token payload:', decoded);

JWT Token Pairs

For enhanced security, use access/refresh token pairs:

// Create token pair
const { accessToken, refreshToken } = jwt.createTokenPair(
  { userId: 123, role: 'user' },
  'your-secret-key',
  {
    accessTokenExpiry: '15m',  // Short-lived access token
    refreshTokenExpiry: '7d'   // Long-lived refresh token
  }
);

// Refresh access token
app.post('/auth/refresh', async (qwe) => {
  const { refreshToken } = qwe.body;
  
  try {
    const newAccessToken = jwt.refreshAccessToken(
      refreshToken,
      process.env.JWT_SECRET!,
      { userId: user.id, role: user.role },
      '15m'
    );
    
    return qwe.success('Token refreshed', { accessToken: newAccessToken });
  } catch (error) {
    return qwe.unauthorized('Invalid refresh token');
  }
});

Middleware System

JWT Middleware

Protect routes with JWT authentication middleware:

import { jwtMiddleware } from 'qwe-framework';

// Global JWT protection
app.use(jwtMiddleware({
  secret: process.env.JWT_SECRET!,
  algorithms: ['HS256'],
  issuer: 'my-app',
  audience: 'my-api'
}));

// Route-specific protection
app.get('/api/profile', 
  jwtMiddleware({ secret: process.env.JWT_SECRET! }),
  (qwe) => {
    // User data available in qwe.user
    return qwe.success('Profile data', { user: qwe.user });
  }
);

Custom Token Extraction

Configure how tokens are extracted from requests:

app.use(jwtMiddleware({
  secret: process.env.JWT_SECRET!,
  getToken: (qwe) => {
    // Try multiple sources
    return qwe.headers['x-auth-token'] ||      // Custom header
           qwe.query['access_token'] ||         // Query parameter
           qwe.cookies['jwt'] ||                // Cookie
           qwe.headers['authorization']?.replace('Bearer ', ''); // Authorization header
  }
}));

Optional Authentication

Some routes may benefit from user context but don't require authentication:

import { optionalAuth } from 'qwe-framework';

app.get('/api/posts',
  optionalAuth({ secret: process.env.JWT_SECRET! }),
  (qwe) => {
    const posts = getPosts();
    
    // Enhance response if user is authenticated
    if (qwe.user) {
      posts.forEach(post => {
        post.isLiked = isPostLikedByUser(post.id, qwe.user.userId);
      });
    }
    
    return qwe.success('Posts retrieved', posts);
  }
);

Error Handling

Customize authentication error responses:

app.use(jwtMiddleware({
  secret: process.env.JWT_SECRET!,
  onError: (qwe, error) => {
    console.error('Authentication error:', error.message);
    
    // Log security events
    logSecurityEvent({
      type: 'auth_failure',
      ip: qwe.ip,
      userAgent: qwe.headers['user-agent'],
      error: error.message
    });
  }
}));

Authorization Patterns

Role-Based Access Control

Protect routes based on user roles:

import { requireRoles } from 'qwe-framework';

// Single role requirement
app.get('/admin/users',
  jwtMiddleware({ secret: process.env.JWT_SECRET! }),
  requireRoles('admin'),
  (qwe) => {
    return qwe.success('Admin users', getAdminUsers());
  }
);

// Multiple role options
app.get('/admin/dashboard',
  jwtMiddleware({ secret: process.env.JWT_SECRET! }),
  requireRoles('admin', 'moderator', 'supervisor'),
  (qwe) => {
    return qwe.success('Dashboard data', getDashboardData());
  }
);

Permission-Based Access Control

Fine-grained permissions for specific actions:

import { requirePermissions } from 'qwe-framework';

// Single permission
app.post('/api/posts',
  jwtMiddleware({ secret: process.env.JWT_SECRET! }),
  requirePermissions('posts:create'),
  (qwe) => {
    const post = createPost(qwe.body, qwe.user.userId);
    return qwe.created('Post created', post);
  }
);

// Multiple permissions (all required)
app.delete('/api/posts/:id',
  jwtMiddleware({ secret: process.env.JWT_SECRET! }),
  requirePermissions('posts:delete', 'posts:moderate'),
  (qwe) => {
    deletePost(qwe.params.id);
    return qwe.success('Post deleted');
  }
);

Resource Ownership

Protect resources based on ownership:

import { requireOwnershipOrRole } from 'qwe-framework';

// Only owner or admin can access
app.get('/api/users/:id/profile',
  jwtMiddleware({ secret: process.env.JWT_SECRET! }),
  requireOwnershipOrRole(
    (qwe) => qwe.params.id,  // Get resource owner ID
    ['admin', 'moderator']   // Admin roles that can access any resource
  ),
  (qwe) => {
    const profile = getUserProfile(qwe.params.id);
    return qwe.success('Profile retrieved', profile);
  }
);

// Custom ownership logic
app.put('/api/posts/:id',
  jwtMiddleware({ secret: process.env.JWT_SECRET! }),
  requireOwnershipOrRole(
    async (qwe) => {
      // Get post owner from database
      const post = await getPost(qwe.params.id);
      return post.authorId;
    },
    ['admin', 'moderator']
  ),
  (qwe) => {
    const updatedPost = updatePost(qwe.params.id, qwe.body);
    return qwe.success('Post updated', updatedPost);
  }
);

Conditional Authorization

Apply different rules based on context:

// Custom authorization middleware
const requireOwnerOrPublic = async (qwe: QweContext, next: () => Promise<void>) => {
  const resource = await getResource(qwe.params.id);
  
  // Public resources don't need ownership check
  if (resource.isPublic) {
    await next();
    return;
  }
  
  // Private resources need authentication
  if (!qwe.user) {
    return qwe.unauthorized('Authentication required for private resources');
  }
  
  // Check ownership or admin role
  if (resource.ownerId !== qwe.user.userId && !qwe.user.roles?.includes('admin')) {
    return qwe.forbidden('Access denied');
  }
  
  await next();
};

app.get('/api/resources/:id',
  optionalAuth({ secret: process.env.JWT_SECRET! }),
  requireOwnerOrPublic,
  (qwe) => {
    const resource = getResource(qwe.params.id);
    return qwe.success('Resource retrieved', resource);
  }
);

Security Best Practices

Token Security

// 1. Use strong secrets
const jwtSecret = process.env.JWT_SECRET || generateStrongSecret();

// 2. Short-lived access tokens
const accessToken = jwt.sign(payload, jwtSecret, { expiresIn: '15m' });

// 3. Secure token storage
app.post('/auth/login', (qwe) => {
  const token = generateToken(user);
  
  // Set secure cookie
  qwe.cookie('jwt', token, {
    httpOnly: true,     // Prevent XSS
    secure: true,       // HTTPS only
    sameSite: 'strict', // CSRF protection
    maxAge: 15 * 60 * 1000 // 15 minutes
  });
  
  return qwe.success('Login successful', { user });
});

Token Revocation

Implement token blacklisting for enhanced security:

// Token revocation store
const revokedTokens = new Set<string>();

// Revoke token
app.post('/auth/logout', 
  jwtMiddleware({ secret: process.env.JWT_SECRET! }),
  (qwe) => {
    const token = extractTokenFromRequest(qwe);
    revokedTokens.add(token);
    
    // Clear cookie
    qwe.cookie('jwt', '', { maxAge: 0 });
    
    return qwe.success('Logged out successfully');
  }
);

// Check revocation in middleware
app.use(jwtMiddleware({
  secret: process.env.JWT_SECRET!,
  isRevoked: (qwe, payload) => {
    const token = extractTokenFromRequest(qwe);
    return revokedTokens.has(token);
  }
}));

Rate Limiting for Auth Endpoints

Protect authentication endpoints from brute force attacks:

import { rateLimiter } from 'qwe-framework';

// Strict rate limiting for login attempts
app.use('/auth/login', rateLimiter({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5,                   // 5 attempts per window
  message: 'Too many login attempts, please try again later',
  keyGenerator: (qwe) => qwe.ip // Rate limit per IP
}));

// Different limits for password reset
app.use('/auth/reset-password', rateLimiter({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 3,                   // 3 attempts per hour
  keyGenerator: (qwe) => qwe.body.email || qwe.ip
}));

Input Validation

Always validate authentication-related inputs:

const loginSchema = qwe.validate.object({
  email: qwe.validate.string().email().required(),
  password: qwe.validate.string().min(8).required()
});

app.post('/auth/login', async (qwe) => {
  // Validate input
  const validation = await qwe.validate(loginSchema, qwe.body);
  if (!validation.success) {
    return qwe.badRequest('Invalid input', validation.errors);
  }
  
  const { email, password } = validation.data;
  
  // Authenticate user
  const user = await authenticateUser(email, password);
  if (!user) {
    return qwe.unauthorized('Invalid credentials');
  }
  
  // Continue with token generation...
});

Production Considerations

Environment Configuration

// Environment-specific JWT configuration
const jwtConfig = {
  secret: process.env.JWT_SECRET!,
  accessTokenExpiry: process.env.NODE_ENV === 'production' ? '15m' : '1h',
  refreshTokenExpiry: process.env.NODE_ENV === 'production' ? '7d' : '30d',
  algorithms: ['HS256'] as const,
  issuer: process.env.JWT_ISSUER || 'qwe-app',
  audience: process.env.JWT_AUDIENCE || 'qwe-api'
};

Monitoring & Logging

Track authentication events for security monitoring:

const authLogger = {
  loginSuccess: (userId: string, ip: string) => {
    console.log(`[AUTH] Login success: User ${userId} from ${ip}`);
  },
  loginFailure: (email: string, ip: string, reason: string) => {
    console.warn(`[AUTH] Login failed: ${email} from ${ip} - ${reason}`);
  },
  tokenExpired: (userId: string, ip: string) => {
    console.log(`[AUTH] Token expired: User ${userId} from ${ip}`);
  },
  suspiciousActivity: (details: any) => {
    console.error(`[SECURITY] Suspicious activity detected:`, details);
  }
};

// Use in middleware
app.use(jwtMiddleware({
  secret: process.env.JWT_SECRET!,
  onError: (qwe, error) => {
    if (error.message.includes('expired')) {
      authLogger.tokenExpired(qwe.user?.userId || 'unknown', qwe.ip);
    } else {
      authLogger.suspiciousActivity({
        ip: qwe.ip,
        userAgent: qwe.headers['user-agent'],
        error: error.message,
        url: qwe.url
      });
    }
  }
}));

Health Checks

Monitor authentication system health:

app.get('/health/auth', (qwe) => {
  const healthCheck = {
    timestamp: new Date().toISOString(),
    status: 'healthy',
    checks: {
      jwtSecret: !!process.env.JWT_SECRET,
      tokenGeneration: false,
      tokenVerification: false
    }
  };
  
  try {
    // Test token generation
    const testToken = qwe.jwt.sign({ test: true }, process.env.JWT_SECRET!, { expiresIn: '1s' });
    healthCheck.checks.tokenGeneration = true;
    
    // Test token verification
    qwe.jwt.verify(testToken, process.env.JWT_SECRET!);
    healthCheck.checks.tokenVerification = true;
  } catch (error) {
    healthCheck.status = 'unhealthy';
  }
  
  return qwe.success('Auth health check', healthCheck);
});

Examples & Use Cases

Complete Authentication Flow

import { createApp, jwtMiddleware, requireRoles, rateLimiter } from 'qwe-framework';

const app = createApp();

// Rate limiting for auth endpoints
app.use('/auth', rateLimiter({ windowMs: 15 * 60 * 1000, max: 10 }));

// User registration
app.post('/auth/register', async (qwe) => {
  const validation = await qwe.validate(registerSchema, qwe.body);
  if (!validation.success) {
    return qwe.badRequest('Validation failed', validation.errors);
  }
  
  const { email, password, name } = validation.data;
  
  // Check if user exists
  const existingUser = await findUserByEmail(email);
  if (existingUser) {
    return qwe.conflict('User already exists');
  }
  
  // Hash password and create user
  const hashedPassword = await qwe.hash.hash(password);
  const user = await createUser({
    email,
    password: hashedPassword,
    name,
    role: 'user'
  });
  
  // Generate tokens
  const accessToken = qwe.jwt.sign(
    { userId: user.id, email: user.email, role: user.role },
    process.env.JWT_SECRET!,
    { expiresIn: '15m' }
  );
  
  const refreshToken = qwe.jwt.sign(
    { userId: user.id, type: 'refresh' },
    process.env.JWT_SECRET!,
    { expiresIn: '7d' }
  );
  
  return qwe.created('User registered successfully', {
    user: { id: user.id, email: user.email, name: user.name },
    accessToken,
    refreshToken
  });
});

// User login
app.post('/auth/login', async (qwe) => {
  const { email, password } = qwe.body;
  
  const user = await findUserByEmail(email);
  if (!user || !await qwe.hash.verify(password, user.password)) {
    return qwe.unauthorized('Invalid credentials');
  }
  
  // Update last login
  await updateUserLastLogin(user.id);
  
  // Generate tokens
  const accessToken = qwe.jwt.sign(
    { userId: user.id, email: user.email, role: user.role },
    process.env.JWT_SECRET!,
    { expiresIn: '15m' }
  );
  
  return qwe.success('Login successful', {
    user: { id: user.id, email: user.email, name: user.name },
    accessToken
  });
});

// Protected user profile
app.get('/api/profile',
  jwtMiddleware({ secret: process.env.JWT_SECRET! }),
  (qwe) => {
    const profile = getUserProfile(qwe.user.userId);
    return qwe.success('Profile retrieved', profile);
  }
);

// Admin-only endpoint
app.get('/api/admin/users',
  jwtMiddleware({ secret: process.env.JWT_SECRET! }),
  requireRoles('admin'),
  (qwe) => {
    const users = getAllUsers();
    return qwe.success('Users retrieved', users);
  }
);

Microservices Authentication

Share authentication across services:

// Shared JWT middleware for microservices
const sharedAuthMiddleware = jwtMiddleware({
  secret: process.env.SHARED_JWT_SECRET!,
  issuer: 'auth-service',
  audience: ['user-service', 'order-service', 'payment-service'],
  algorithms: ['HS256']
});

// User service
app.use('/api/users', sharedAuthMiddleware);
app.get('/api/users/:id', (qwe) => {
  const user = getUser(qwe.params.id);
  return qwe.success('User found', user);
});

// Order service  
app.use('/api/orders', sharedAuthMiddleware);
app.post('/api/orders', requireRoles('customer'), (qwe) => {
  const order = createOrder(qwe.body, qwe.user.userId);
  return qwe.created('Order created', order);
});

Conclusion

The Qwe Framework authentication system provides enterprise-grade security with zero dependencies. The combination of JWT tokens, flexible middleware, and authorization patterns covers all common authentication scenarios while maintaining high performance and developer productivity.

Key Benefits

  • Zero Dependencies: Complete JWT implementation built-in
  • Flexible Authorization: Role, permission, and ownership-based access
  • Security First: Built-in protections and best practices
  • High Performance: Optimized for speed and efficiency
  • Developer Friendly: Clean APIs and comprehensive error handling

Next Steps


Need help? Check the API Reference for complete JWT and middleware documentation.