Authentication dan Authorization (Konsep) - Perwira Learning Center


1. Latar Belakang

    Memasuki minggu keempat, hari ketiga masih belajar Express.js dan akan membahas Authentication dan Authorization. Ini adalah sistem keamanan dan hak akses dalam aplikasi:

Analogi Bandara Internasional:

  • Authentication (Autentikasi) = Pemeriksaan paspor di imigrasi 
    • "Siapa kamu? Tunjukkan identitasmu!"
    • Buktikan bahwa kamu adalah benar-benar dirimu
    • Hasil: Kamu terverifikasi sebagai penumpang sah
  • Authorization (Otorisasi) = Boarding pass dan akses lounge
    •  "Apa yang boleh kamu lakukan?"
    • Setelah identitas jelas, tentukan aksesnya
    • Hasil: Kamu boleh masuk ke lounge, naik pesawat, dll

Perbedaan Fundamental:

text
┌─────────────────┐     ┌─────────────────┐
│  AUTHENTICATION │     │  AUTHORIZATION  │
│   (Login/Logout)│     │   (Permissions) │
├─────────────────┤     ├─────────────────┤
│ "Siapa kamu?"   │     │ "Apa yang bisa │
│                 │     │  kamu lakukan?" │
├─────────────────┤     ├─────────────────┤
│ Identitas User  │     │  Hak Akses     │
│ Email/Password  │     │  Role/Permission│
├─────────────────┤     ├─────────────────┤
│ Dilakukan sekali│     │  Dilakukan setiap│
│ per sesi        │     │  request        │
└─────────────────┘     └─────────────────┘
        │                       │
        ▼                       ▼
   Verified User         Authorized Actions

2. Alat dan Bahan

a. Perangkat Lunak

  • Node.js & npm - Runtime dan package manager
  • Express.js - Framework utama
  • jsonwebtoken - Library JWT
  • bcryptjs - Hashing password
  • crypto - Node.js built-in untuk enkripsi
  • express-session - Session management
  • Postman - Testing endpoints
  • VS Code - Code editor

b. Perangkat Keras

  • Laptop/PC dengan spesifikasi standar

3. Pembahasan

3.1 Authentication vs Authorization - Visualisasi

javascript
/**
 * VISUALISASI AUTHENTICATION vs AUTHORIZATION
 * 
 * AUTHENTICATION (Login):
 * ┌─────────────────────────────────────────────────────┐
 * │   Request: POST /login                             │
 * │   {                                                │
 * │     "email": "user@example.com",                  │
 * │     "password": "********"                        │
 * │   }                                               │
 * └─────────────────────────────────────────────────────┘
 *                            │
 *                            ▼
 * ┌─────────────────────────────────────────────────────┐
 * │   VERIFICATION PROCESS:                            │
 * │   1. Cek email ada?                               │
 * │   2. Cek password cocok?                          │
 * │   3. Akun masih aktif?                            │
 * │   4. Generate JWT Token                           │
 * └─────────────────────────────────────────────────────┘
 *                            │
 *                            ▼
 * ┌─────────────────────────────────────────────────────┐
 * │   Response: 200 OK                                 │
 * │   {                                                │
 * │     "token": "eyJhbGciOiJIUzI1NiIs...",           │
 * │     "user": { id: 1, name: "John" }               │
 * │   }                                               │
 * └─────────────────────────────────────────────────────┘
 * 
 * 
 * AUTHORIZATION (Access Control):
 * ┌─────────────────────────────────────────────────────┐
 * │   Request: GET /admin/users                        │
 * │   Headers: { Authorization: "Bearer <token>" }    │
 * └─────────────────────────────────────────────────────┘
 *                            │
 *                            ▼
 * ┌─────────────────────────────────────────────────────┐
 * │   AUTHORIZATION CHECK:                             │
 * │   1. Verify token valid?                           │
 * │   2. Extract user role (admin/user/guest)         │
 * │   3. Check if role has permission?                 │
 * │   4. ✅ Allow | ❌ Deny                           │
 * └─────────────────────────────────────────────────────┘
 *                            │
 *                            ▼
 * ┌─────────────────────────────────────────────────────┐
 * │   Response: 200 OK | 403 Forbidden                │
 * │   {                                               │
 * │     "data": [...] | "error": "Forbidden"         │
 * │   }                                              │
 * └─────────────────────────────────────────────────────┘
 */

3.2 Praktik Lengkap: Sistem Authentication & Authorization

Mari kita bangun sistem autentikasi komprehensif dari nol:

bash
# 1. Buat folder project
mkdir auth-system
cd auth-system

# 2. Inisialisasi project
npm init -y

# 3. Install dependencies
npm install express dotenv cors helmet morgan compression
npm install jsonwebtoken bcryptjs crypto
npm install express-session redis connect-redis
npm install joi express-validator
npm install -D nodemon

# 4. Buat struktur folder
mkdir -p src/{models,controllers,routes,middleware,config,services,utils}

A. Config & Environment

src/config/env.js

javascript
/**
 * ENVIRONMENT CONFIGURATION
 * Semua konfigurasi environment disimpan di sini
 */

require('dotenv').config();

module.exports = {
  // Server
  NODE_ENV: process.env.NODE_ENV || 'development',
  PORT: parseInt(process.env.PORT) || 3000,
  
  // JWT Configuration
  JWT: {
    SECRET: process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production',
    REFRESH_SECRET: process.env.JWT_REFRESH_SECRET || 'your-refresh-secret-key',
    ACCESS_EXPIRE: process.env.JWT_ACCESS_EXPIRE || '15m', // 15 minutes
    REFRESH_EXPIRE: process.env.JWT_REFRESH_EXPIRE || '7d', // 7 days
    ISSUER: process.env.JWT_ISSUER || 'auth-system',
    AUDIENCE: process.env.JWT_AUDIENCE || 'auth-system-client'
  },
  
  // Session Configuration
  SESSION: {
    SECRET: process.env.SESSION_SECRET || 'session-secret-key',
    TTL: parseInt(process.env.SESSION_TTL) || 86400, // 24 hours
    SECURE: process.env.NODE_ENV === 'production'
  },
  
  // Redis Configuration (untuk session store)
  REDIS: {
    URL: process.env.REDIS_URL || 'redis://localhost:6379',
    ENABLED: process.env.REDIS_ENABLED === 'true' || false
  },
  
  // Password Configuration
  PASSWORD: {
    SALT_ROUNDS: parseInt(process.env.BCRYPT_SALT_ROUNDS) || 10,
    MIN_LENGTH: parseInt(process.env.PASSWORD_MIN_LENGTH) || 8,
    REQUIRE_UPPERCASE: process.env.PASSWORD_REQUIRE_UPPERCASE !== 'false',
    REQUIRE_LOWERCASE: process.env.PASSWORD_REQUIRE_LOWERCASE !== 'false',
    REQUIRE_NUMBERS: process.env.PASSWORD_REQUIRE_NUMBERS !== 'false',
    REQUIRE_SPECIAL: process.env.PASSWORD_REQUIRE_SPECIAL !== 'false'
  },
  
  // Rate Limiting untuk auth endpoints
  RATE_LIMIT: {
    LOGIN: {
      WINDOW_MS: 15 * 60 * 1000, // 15 minutes
      MAX_ATTEMPTS: 5 // 5 attempts per window
    },
    REGISTER: {
      WINDOW_MS: 60 * 60 * 1000, // 1 hour
      MAX_ATTEMPTS: 3 // 3 registrations per hour
    }
  }
};

B. Models untuk Authentication

src/models/User.model.js

javascript
/**
 * USER MODEL
 * Representasi data user dan method autentikasi
 */

const bcrypt = require('bcryptjs');
const crypto = require('crypto');
const config = require('../config/env');

class User {
  constructor(userData = {}) {
    this.id = userData.id || null;
    this.email = userData.email || '';
    this.password = userData.password || '';
    this.name = userData.name || '';
    this.role = userData.role || 'user';
    this.permissions = userData.permissions || [];
    this.isActive = userData.isActive !== undefined ? userData.isActive : true;
    this.isEmailVerified = userData.isEmailVerified || false;
    this.emailVerificationToken = userData.emailVerificationToken || null;
    this.passwordResetToken = userData.passwordResetToken || null;
    this.passwordResetExpires = userData.passwordResetExpires || null;
    this.loginAttempts = userData.loginAttempts || 0;
    this.lockUntil = userData.lockUntil || null;
    this.lastLogin = userData.lastLogin || null;
    this.createdAt = userData.createdAt || new Date();
    this.updatedAt = userData.updatedAt || new Date();
    this.refreshTokens = userData.refreshTokens || [];
  }

  /**
   * Hash password sebelum disimpan
   */
  static async hashPassword(password) {
    return await bcrypt.hash(password, config.PASSWORD.SALT_ROUNDS);
  }

  /**
   * Validasi password
   */
  async validatePassword(password) {
    return await bcrypt.compare(password, this.password);
  }

  /**
   * Generate email verification token
   */
  generateEmailVerificationToken() {
    const token = crypto.randomBytes(32).toString('hex');
    
    this.emailVerificationToken = crypto
      .createHash('sha256')
      .update(token)
      .digest('hex');
    
    return token;
  }

  /**
   * Verify email token
   */
  verifyEmailToken(token) {
    const hashedToken = crypto
      .createHash('sha256')
      .update(token)
      .digest('hex');
    
    return this.emailVerificationToken === hashedToken;
  }

  /**
   * Generate password reset token
   */
  generatePasswordResetToken() {
    const token = crypto.randomBytes(32).toString('hex');
    
    this.passwordResetToken = crypto
      .createHash('sha256')
      .update(token)
      .digest('hex');
    
    this.passwordResetExpires = Date.now() + 3600000; // 1 hour
    
    return token;
  }

  /**
   * Verify password reset token
   */
  verifyPasswordResetToken(token) {
    const hashedToken = crypto
      .createHash('sha256')
      .update(token)
      .digest('hex');
    
    return this.passwordResetToken === hashedToken && 
           this.passwordResetExpires > Date.now();
  }

  /**
   * Increment login attempts
   */
  incrementLoginAttempts() {
    this.loginAttempts += 1;
    
    // Lock account after 5 failed attempts
    if (this.loginAttempts >= 5) {
      this.lockUntil = Date.now() + 30 * 60 * 1000; // 30 minutes
    }
  }

  /**
   * Reset login attempts on successful login
   */
  resetLoginAttempts() {
    this.loginAttempts = 0;
    this.lockUntil = null;
    this.lastLogin = new Date();
  }

  /**
   * Check if account is locked
   */
  isLocked() {
    return this.lockUntil && this.lockUntil > Date.now();
  }

  /**
   * Add refresh token
   */
  addRefreshToken(token, expiresIn = '7d') {
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 7); // 7 days
    
    this.refreshTokens.push({
      token,
      expiresAt,
      createdAt: new Date()
    });
    
    // Keep only last 5 refresh tokens
    if (this.refreshTokens.length > 5) {
      this.refreshTokens = this.refreshTokens.slice(-5);
    }
  }

  /**
   * Revoke refresh token
   */
  revokeRefreshToken(token) {
    this.refreshTokens = this.refreshTokens.filter(t => t.token !== token);
  }

  /**
   * Check if refresh token is valid
   */
  isValidRefreshToken(token) {
    const refreshToken = this.refreshTokens.find(t => t.token === token);
    
    if (!refreshToken) return false;
    if (refreshToken.expiresAt < new Date()) return false;
    
    return true;
  }

  /**
   * Remove sensitive data before sending to client
   */
  toJSON() {
    return {
      id: this.id,
      email: this.email,
      name: this.name,
      role: this.role,
      permissions: this.permissions,
      isActive: this.isActive,
      isEmailVerified: this.isEmailVerified,
      lastLogin: this.lastLogin,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt
    };
  }
}

/**
 * DATABASE SIMULASI
 * Dalam produksi, ini akan diganti dengan MongoDB/PostgreSQL
 */
const users = [];
let nextId = 1;

// Seed data
const seedAdmin = async () => {
  const hashedPassword = await User.hashPassword('Admin123!');
  const admin = new User({
    id: nextId++,
    email: 'admin@system.com',
    password: hashedPassword,
    name: 'System Administrator',
    role: 'admin',
    permissions: ['*'], // Super admin, all permissions
    isEmailVerified: true
  });
  users.push(admin);
  
  const hashedUserPassword = await User.hashPassword('User123!');
  const user = new User({
    id: nextId++,
    email: 'user@example.com',
    password: hashedUserPassword,
    name: 'Regular User',
    role: 'user',
    permissions: ['read:profile', 'update:profile'],
    isEmailVerified: true
  });
  users.push(user);
  
  const hashedModPassword = await User.hashPassword('Mod123!');
  const moderator = new User({
    id: nextId++,
    email: 'mod@system.com',
    password: hashedModPassword,
    name: 'Content Moderator',
    role: 'moderator',
    permissions: [
      'read:posts',
      'create:posts', 
      'update:posts',
      'delete:posts',
      'read:users',
      'update:users'
    ],
    isEmailVerified: true
  });
  users.push(moderator);
};

seedAdmin();

module.exports = {
  User,
  users,
  nextId: () => nextId++
};

C. Service Layer untuk Authentication

src/services/auth.service.js

javascript
/**
 * AUTHENTICATION SERVICE
 * Business logic untuk semua operasi autentikasi
 */

const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const { User, users, nextId } = require('../models/User.model');
const config = require('../config/env');
const logger = require('../utils/logger');

class AuthService {
  constructor() {
    this.jwtSecret = config.JWT.SECRET;
    this.refreshSecret = config.JWT.REFRESH_SECRET;
    this.accessExpire = config.JWT.ACCESS_EXPIRE;
    this.refreshExpire = config.JWT.REFRESH_EXPIRE;
  }

  /**
   * ============= AUTHENTICATION =============
   * Memverifikasi identitas user
   */

  /**
   * Register user baru
   */
  async register(userData) {
    try {
      // Validasi input
      this.validateRegistrationData(userData);

      // Cek email sudah terdaftar
      const existingUser = users.find(u => u.email === userData.email);
      if (existingUser) {
        throw {
          status: 409,
          code: 'EMAIL_EXISTS',
          message: 'Email sudah terdaftar'
        };
      }

      // Hash password
      const hashedPassword = await User.hashPassword(userData.password);

      // Buat user baru
      const newUser = new User({
        id: nextId(),
        email: userData.email.toLowerCase(),
        password: hashedPassword,
        name: userData.name,
        role: userData.role || 'user',
        permissions: this.getDefaultPermissions(userData.role || 'user'),
        isEmailVerified: false // Harus verifikasi email
      });

      // Generate email verification token
      const verificationToken = newUser.generateEmailVerificationToken();

      users.push(newUser);

      // Log activity
      logger.info(`User registered: ${newUser.email}`, { 
        userId: newUser.id,
        role: newUser.role 
      });

      // Generate access token
      const accessToken = this.generateAccessToken(newUser);
      const refreshToken = this.generateRefreshToken(newUser);

      // Simpan refresh token
      newUser.addRefreshToken(refreshToken);

      // Kirim email verifikasi (simulasi)
      await this.sendVerificationEmail(newUser.email, verificationToken);

      return {
        user: newUser.toJSON(),
        tokens: {
          accessToken,
          refreshToken,
          expiresIn: this.getExpiresIn(this.accessExpire)
        },
        requiresEmailVerification: true
      };
    } catch (error) {
      logger.error('Registration error:', error);
      throw error;
    }
  }

  /**
   * Login user
   */
  async login(email, password, deviceInfo = {}) {
    try {
      // Validasi input
      if (!email || !password) {
        throw {
          status: 400,
          code: 'MISSING_CREDENTIALS',
          message: 'Email dan password wajib diisi'
        };
      }

      // Cari user
      const user = users.find(u => u.email === email.toLowerCase());
      
      if (!user) {
        throw {
          status: 401,
          code: 'INVALID_CREDENTIALS',
          message: 'Email atau password salah'
        };
      }

      // Cek account lock
      if (user.isLocked()) {
        const lockTimeRemaining = Math.ceil((user.lockUntil - Date.now()) / 60000);
        throw {
          status: 423,
          code: 'ACCOUNT_LOCKED',
          message: `Akun terkunci. Coba lagi dalam ${lockTimeRemaining} menit`,
          lockTimeRemaining
        };
      }

      // Validasi password
      const isValidPassword = await user.validatePassword(password);
      
      if (!isValidPassword) {
        user.incrementLoginAttempts();
        
        // Log failed attempt
        logger.warn(`Failed login attempt for: ${email}`, {
          attempts: user.loginAttempts,
          ip: deviceInfo.ip
        });

        throw {
          status: 401,
          code: 'INVALID_CREDENTIALS',
          message: 'Email atau password salah',
          remainingAttempts: 5 - user.loginAttempts
        };
      }

      // Cek account aktif
      if (!user.isActive) {
        throw {
          status: 403,
          code: 'ACCOUNT_INACTIVE',
          message: 'Akun tidak aktif. Hubungi administrator'
        };
      }

      // Cek email verification
      if (!user.isEmailVerified) {
        throw {
          status: 403,
          code: 'EMAIL_NOT_VERIFIED',
          message: 'Email belum diverifikasi',
          resendVerification: true
        };
      }

      // Reset login attempts on success
      user.resetLoginAttempts();

      // Generate tokens
      const accessToken = this.generateAccessToken(user);
      const refreshToken = this.generateRefreshToken(user);

      // Simpan refresh token
      user.addRefreshToken(refreshToken);

      // Log successful login
      logger.info(`User logged in: ${user.email}`, {
        userId: user.id,
        deviceInfo
      });

      return {
        user: user.toJSON(),
        tokens: {
          accessToken,
          refreshToken,
          expiresIn: this.getExpiresIn(this.accessExpire)
        }
      };
    } catch (error) {
      logger.error('Login error:', error);
      throw error;
    }
  }

  /**
   * Refresh token
   */
  async refreshToken(refreshToken) {
    try {
      if (!refreshToken) {
        throw {
          status: 401,
          code: 'NO_REFRESH_TOKEN',
          message: 'Refresh token required'
        };
      }

      // Verify refresh token
      const decoded = jwt.verify(refreshToken, this.refreshSecret);

      // Find user
      const user = users.find(u => u.id === decoded.sub);
      
      if (!user) {
        throw {
          status: 401,
          code: 'USER_NOT_FOUND',
          message: 'User tidak ditemukan'
        };
      }

      // Check if refresh token is valid
      if (!user.isValidRefreshToken(refreshToken)) {
        throw {
          status: 401,
          code: 'INVALID_REFRESH_TOKEN',
          message: 'Refresh token tidak valid'
        };
      }

      // Generate new tokens
      const newAccessToken = this.generateAccessToken(user);
      const newRefreshToken = this.generateRefreshToken(user);

      // Revoke old refresh token and add new one
      user.revokeRefreshToken(refreshToken);
      user.addRefreshToken(newRefreshToken);

      return {
        tokens: {
          accessToken: newAccessToken,
          refreshToken: newRefreshToken,
          expiresIn: this.getExpiresIn(this.accessExpire)
        }
      };
    } catch (error) {
      if (error.name === 'TokenExpiredError') {
        throw {
          status: 401,
          code: 'REFRESH_TOKEN_EXPIRED',
          message: 'Refresh token expired. Silakan login ulang'
        };
      }
      
      if (error.name === 'JsonWebTokenError') {
        throw {
          status: 401,
          code: 'INVALID_REFRESH_TOKEN',
          message: 'Refresh token tidak valid'
        };
      }

      throw error;
    }
  }

  /**
   * Logout user
   */
  async logout(userId, refreshToken) {
    try {
      const user = users.find(u => u.id === userId);
      
      if (user && refreshToken) {
        user.revokeRefreshToken(refreshToken);
        
        logger.info(`User logged out: ${user.email}`, {
          userId: user.id
        });
      }

      return { success: true };
    } catch (error) {
      logger.error('Logout error:', error);
      throw error;
    }
  }

  /**
   * ============= AUTHORIZATION =============
   * Memverifikasi hak akses user
   */

  /**
   * Verify JWT token
   */
  verifyToken(token, type = 'access') {
    try {
      const secret = type === 'access' ? this.jwtSecret : this.refreshSecret;
      const decoded = jwt.verify(token, secret, {
        issuer: config.JWT.ISSUER,
        audience: config.JWT.AUDIENCE
      });
      
      return decoded;
    } catch (error) {
      if (error.name === 'TokenExpiredError') {
        throw {
          status: 401,
          code: 'TOKEN_EXPIRED',
          message: 'Token expired'
        };
      }
      
      throw {
        status: 401,
        code: 'INVALID_TOKEN',
        message: 'Token tidak valid'
      };
    }
  }

  /**
   * Check if user has role
   */
  hasRole(user, allowedRoles) {
    if (!user || !user.role) return false;
    
    // Admin has all roles
    if (user.role === 'admin') return true;
    
    return allowedRoles.includes(user.role);
  }

  /**
   * Check if user has permission
   */
  hasPermission(user, requiredPermissions) {
    if (!user || !user.permissions) return false;
    
    // Admin has all permissions
    if (user.role === 'admin' || user.permissions.includes('*')) {
      return true;
    }
    
    // Check each required permission
    return requiredPermissions.every(permission => 
      user.permissions.includes(permission)
    );
  }

  /**
   * Check if user owns resource
   */
  isResourceOwner(user, resourceUserId) {
    return user.id === resourceUserId || user.role === 'admin';
  }

  /**
   * Get user permissions
   */
  getUserPermissions(user) {
    if (user.role === 'admin') {
      return ['*']; // Super admin
    }
    
    return user.permissions || [];
  }

  /**
   * ============= HELPER METHODS =============
   */

  /**
   * Generate access token
   */
  generateAccessToken(user) {
    const payload = {
      sub: user.id,
      email: user.email,
      role: user.role,
      permissions: this.getUserPermissions(user),
      iss: config.JWT.ISSUER,
      aud: config.JWT.AUDIENCE,
      iat: Math.floor(Date.now() / 1000)
    };

    return jwt.sign(payload, this.jwtSecret, {
      expiresIn: this.accessExpire
    });
  }

  /**
   * Generate refresh token
   */
  generateRefreshToken(user) {
    const payload = {
      sub: user.id,
      type: 'refresh',
      iss: config.JWT.ISSUER,
      aud: config.JWT.AUDIENCE,
      iat: Math.floor(Date.now() / 1000)
    };

    return jwt.sign(payload, this.refreshSecret, {
      expiresIn: this.refreshExpire
    });
  }

  /**
   * Get default permissions based on role
   */
  getDefaultPermissions(role) {
    const permissions = {
      admin: ['*'],
      moderator: [
        'read:posts',
        'create:posts',
        'update:posts',
        'delete:posts',
        'read:users',
        'update:users'
      ],
      user: [
        'read:profile',
        'update:profile',
        'read:posts',
        'create:posts',
        'update:own_posts',
        'delete:own_posts'
      ]
    };

    return permissions[role] || [];
  }

  /**
   * Validate registration data
   */
  validateRegistrationData(data) {
    const errors = [];

    if (!data.email) {
      errors.push('Email wajib diisi');
    } else if (!this.isValidEmail(data.email)) {
      errors.push('Format email tidak valid');
    }

    if (!data.password) {
      errors.push('Password wajib diisi');
    } else {
      const passwordErrors = this.validatePasswordStrength(data.password);
      errors.push(...passwordErrors);
    }

    if (!data.name) {
      errors.push('Nama wajib diisi');
    } else if (data.name.length < 2) {
      errors.push('Nama minimal 2 karakter');
    }

    if (errors.length > 0) {
      throw {
        status: 400,
        code: 'VALIDATION_ERROR',
        message: 'Validasi registrasi gagal',
        errors
      };
    }
  }

  /**
   * Validate password strength
   */
  validatePasswordStrength(password) {
    const errors = [];

    if (password.length < config.PASSWORD.MIN_LENGTH) {
      errors.push(`Password minimal ${config.PASSWORD.MIN_LENGTH} karakter`);
    }

    if (config.PASSWORD.REQUIRE_UPPERCASE && !/[A-Z]/.test(password)) {
      errors.push('Password harus mengandung huruf besar');
    }

    if (config.PASSWORD.REQUIRE_LOWERCASE && !/[a-z]/.test(password)) {
      errors.push('Password harus mengandung huruf kecil');
    }

    if (config.PASSWORD.REQUIRE_NUMBERS && !/\d/.test(password)) {
      errors.push('Password harus mengandung angka');
    }

    if (config.PASSWORD.REQUIRE_SPECIAL && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
      errors.push('Password harus mengandung karakter khusus');
    }

    return errors;
  }

  /**
   * Validate email format
   */
  isValidEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }

  /**
   * Get expires in seconds
   */
  getExpiresIn(expireString) {
    const unit = expireString.slice(-1);
    const value = parseInt(expireString.slice(0, -1));
    
    if (unit === 'm') return value * 60;
    if (unit === 'h') return value * 3600;
    if (unit === 'd') return value * 86400;
    return 900; // Default 15 minutes
  }

  /**
   * Send verification email (simulasi)
   */
  async sendVerificationEmail(email, token) {
    // Dalam produksi: kirim email real
    console.log(`
      ========== EMAIL VERIFICATION ==========
      To: ${email}
      Subject: Verify Your Email
      
      Click the link below to verify your email:
      http://localhost:3000/api/auth/verify-email?token=${token}
      
      This link will expire in 24 hours.
      =========================================
    `);
    
    return true;
  }

  /**
   * Send password reset email (simulasi)
   */
  async sendPasswordResetEmail(email, token) {
    console.log(`
      ========== PASSWORD RESET ==========
      To: ${email}
      Subject: Reset Your Password
      
      Click the link below to reset your password:
      http://localhost:3000/api/auth/reset-password?token=${token}
      
      This link will expire in 1 hour.
      =====================================
    `);
    
    return true;
  }
}

module.exports = new AuthService();

D. Permission System (RBAC + PBAC)

src/services/permission.service.js

javascript
/**
 * PERMISSION SERVICE
 * Role-Based Access Control (RBAC) + Permission-Based Access Control (PBAC)
 */

class PermissionService {
  constructor() {
    // Define permissions matrix
    this.permissionMatrix = {
      // Admin has all permissions
      admin: ['*'],
      
      // Moderator permissions
      moderator: [
        'read:posts',
        'create:posts',
        'update:posts',
        'delete:posts',
        'read:users',
        'update:users',
        'moderate:comments',
        'read:analytics'
      ],
      
      // Regular user permissions
      user: [
        'read:profile',
        'update:profile',
        'read:posts',
        'create:posts',
        'update:own_posts',
        'delete:own_posts',
        'create:comments',
        'update:own_comments',
        'delete:own_comments'
      ],
      
      // Guest permissions
      guest: [
        'read:posts',
        'read:public_profile'
      ]
    };

    // Define resource ownership rules
    this.ownershipRules = {
      'posts': (user, resource) => user.id === resource.authorId,
      'comments': (user, resource) => user.id === resource.userId,
      'profile': (user, resource) => user.id === resource.userId,
      'orders': (user, resource) => user.id === resource.userId
    };
  }

  /**
   * Check if user has role
   */
  hasRole(user, requiredRoles) {
    if (!user || !user.role) return false;
    
    // Admin has all roles
    if (user.role === 'admin') return true;
    
    return requiredRoles.includes(user.role);
  }

  /**
   * Check if user has permission
   */
  hasPermission(user, requiredPermissions, options = {}) {
    if (!user || !user.permissions) return false;
    
    const permissions = user.permissions;
    
    // Admin has all permissions
    if (user.role === 'admin' || permissions.includes('*')) {
      return true;
    }

    // Check each required permission
    return requiredPermissions.every(permission => {
      // Check direct permission
      if (permissions.includes(permission)) {
        return true;
      }

      // Check wildcard permissions (e.g., 'read:*' matches 'read:posts')
      const [action, resource] = permission.split(':');
      
      return permissions.some(p => {
        const [pAction, pResource] = p.split(':');
        
        if (pAction === action && pResource === '*') {
          return true;
        }
        
        if (pAction === '*' && pResource === resource) {
          return true;
        }
        
        return false;
      });
    });
  }

  /**
   * Check if user owns resource
   */
  isOwner(user, resourceType, resource) {
    const rule = this.ownershipRules[resourceType];
    
    if (!rule) {
      return false;
    }

    return rule(user, resource);
  }

  /**
   * Check if user can access resource
   */
  canAccess(user, permission, resourceType = null, resource = null) {
    // First check permission
    if (!this.hasPermission(user, [permission])) {
      return false;
    }

    // If resource specified, check ownership
    if (resourceType && resource) {
      // Admin can access any resource
      if (user.role === 'admin') {
        return true;
      }

      // Check if permission is for own resources
      if (permission.includes('own_')) {
        return this.isOwner(user, resourceType, resource);
      }
    }

    return true;
  }

  /**
   * Get user's effective permissions
   */
  getEffectivePermissions(user) {
    if (user.role === 'admin') {
      return ['*'];
    }

    const rolePermissions = this.permissionMatrix[user.role] || [];
    const customPermissions = user.permissions || [];
    
    // Merge and deduplicate
    return [...new Set([...rolePermissions, ...customPermissions])];
  }

  /**
   * Filter resources based on permissions
   */
  filterAccessibleResources(user, resources, permission, resourceType) {
    if (!resources || !Array.isArray(resources)) {
      return [];
    }

    return resources.filter(resource => {
      // Check permission
      if (!this.hasPermission(user, [permission])) {
        return false;
      }

      // Admin sees all
      if (user.role === 'admin') {
        return true;
      }

      // For 'own' permissions, check ownership
      if (permission.includes('own_')) {
        return this.isOwner(user, resourceType, resource);
      }

      return true;
    });
  }

  /**
   * Get role hierarchy
   */
  getRoleHierarchy() {
    return {
      'admin': 100,
      'moderator': 50,
      'user': 10,
      'guest': 0
    };
  }

  /**
   * Check if role has higher or equal priority
   */
  hasHigherOrEqualRole(userRole, targetRole) {
    const hierarchy = this.getRoleHierarchy();
    
    return hierarchy[userRole] >= hierarchy[targetRole];
  }

  /**
   * Define custom permission rule
   */
  defineRule(resourceType, ruleFunction) {
    this.ownershipRules[resourceType] = ruleFunction;
  }

  /**
   * Define role permissions
   */
  defineRole(role, permissions) {
    this.permissionMatrix[role] = permissions;
  }
}

module.exports = new PermissionService();

E. Authentication Middleware

src/middleware/auth.middleware.js

javascript
/**
 * AUTHENTICATION & AUTHORIZATION MIDDLEWARE
 * Menangani verifikasi token dan hak akses
 */

const AuthService = require('../services/auth.service');
const PermissionService = require('../services/permission.service');
const { users } = require('../models/User.model');
const logger = require('../utils/logger');

class AuthMiddleware {
  /**
   * ============= AUTHENTICATION =============
   */

  /**
   * Authenticate JWT token
   * Middleware untuk memverifikasi token dan mendapatkan user
   */
  authenticate() {
    return async (req, res, next) => {
      try {
        // Extract token dari header
        const token = this.extractToken(req);
        
        if (!token) {
          return res.status(401).json({
            success: false,
            error: 'NO_TOKEN',
            message: 'Authentication required',
            code: 'AUTH_REQUIRED'
          });
        }

        // Verify token
        const decoded = AuthService.verifyToken(token, 'access');

        // Get user from database
        const user = users.find(u => u.id === decoded.sub);
        
        if (!user) {
          return res.status(401).json({
            success: false,
            error: 'USER_NOT_FOUND',
            message: 'User tidak ditemukan'
          });
        }

        // Check if user is active
        if (!user.isActive) {
          return res.status(403).json({
            success: false,
            error: 'ACCOUNT_INACTIVE',
            message: 'Akun tidak aktif'
          });
        }

        // Attach user and token to request
        req.user = user;
        req.token = token;
        req.decodedToken = decoded;

        // Log authenticated request
        logger.debug('User authenticated', {
          userId: user.id,
          role: user.role,
          path: req.path
        });

        next();
      } catch (error) {
        // Handle specific JWT errors
        if (error.code === 'TOKEN_EXPIRED') {
          return res.status(401).json({
            success: false,
            error: 'TOKEN_EXPIRED',
            message: 'Token expired. Silakan refresh token',
            canRefresh: true
          });
        }

        if (error.code === 'INVALID_TOKEN') {
          return res.status(401).json({
            success: false,
            error: 'INVALID_TOKEN',
            message: 'Token tidak valid'
          });
        }

        // Generic error
        return res.status(401).json({
          success: false,
          error: 'AUTH_FAILED',
          message: 'Authentication failed'
        });
      }
    };
  }

  /**
   * Optional authentication
   * Tidak error jika token tidak ada, user = null
   */
  optional() {
    return async (req, res, next) => {
      try {
        const token = this.extractToken(req);
        
        if (token) {
          const decoded = AuthService.verifyToken(token, 'access');
          const user = users.find(u => u.id === decoded.sub);
          
          if (user && user.isActive) {
            req.user = user;
            req.token = token;
          }
        }
        
        next();
      } catch {
        // Silent fail, proceed without user
        next();
      }
    };
  }

  /**
   * Verify email token
   */
  verifyEmailToken() {
    return async (req, res, next) => {
      try {
        const { token } = req.query;
        
        if (!token) {
          return res.status(400).json({
            success: false,
            error: 'NO_TOKEN',
            message: 'Verification token required'
          });
        }

        // Find user by token
        const hashedToken = require('crypto')
          .createHash('sha256')
          .update(token)
          .digest('hex');

        const user = users.find(u => u.emailVerificationToken === hashedToken);

        if (!user) {
          return res.status(400).json({
            success: false,
            error: 'INVALID_TOKEN',
            message: 'Token verifikasi tidak valid atau expired'
          });
        }

        req.userToVerify = user;
        req.verificationToken = token;
        
        next();
      } catch (error) {
        next(error);
      }
    };
  }

  /**
   * ============= AUTHORIZATION =============
   */

  /**
   * Authorize by role
   * Middleware untuk membatasi akses berdasarkan role
   */
  authorize(...allowedRoles) {
    return (req, res, next) => {
      try {
        if (!req.user) {
          return res.status(401).json({
            success: false,
            error: 'NOT_AUTHENTICATED',
            message: 'Authentication required'
          });
        }

        // Check if user has required role
        if (!AuthService.hasRole(req.user, allowedRoles)) {
          logger.warn('Authorization failed - insufficient role', {
            userId: req.user.id,
            userRole: req.user.role,
            requiredRoles: allowedRoles,
            path: req.path
          });

          return res.status(403).json({
            success: false,
            error: 'FORBIDDEN',
            message: 'Anda tidak memiliki akses ke resource ini',
            requiredRoles: allowedRoles,
            yourRole: req.user.role
          });
        }

        next();
      } catch (error) {
        next(error);
      }
    };
  }

  /**
   * Authorize by permission
   * Middleware untuk membatasi akses berdasarkan permission
   */
  hasPermission(...requiredPermissions) {
    return (req, res, next) => {
      try {
        if (!req.user) {
          return res.status(401).json({
            success: false,
            error: 'NOT_AUTHENTICATED',
            message: 'Authentication required'
          });
        }

        // Check if user has required permissions
        if (!PermissionService.hasPermission(req.user, requiredPermissions)) {
          logger.warn('Authorization failed - insufficient permissions', {
            userId: req.user.id,
            userRole: req.user.role,
            requiredPermissions,
            userPermissions: req.user.permissions,
            path: req.path
          });

          return res.status(403).json({
            success: false,
            error: 'FORBIDDEN',
            message: 'Anda tidak memiliki izin untuk melakukan aksi ini',
            requiredPermissions,
            yourPermissions: req.user.permissions || []
          });
        }

        next();
      } catch (error) {
        next(error);
      }
    };
  }

  /**
   * Authorize resource owner
   * Middleware untuk cek kepemilikan resource
   */
  canAccess(resourceType, resourceIdParam = 'id', ownerField = 'userId') {
    return async (req, res, next) => {
      try {
        if (!req.user) {
          return res.status(401).json({
            success: false,
            error: 'NOT_AUTHENTICATED',
            message: 'Authentication required'
          });
        }

        const resourceId = req.params[resourceIdParam];
        
        // Get resource from database (simulasi)
        const resource = await this.getResource(resourceType, resourceId);
        
        if (!resource) {
          return res.status(404).json({
            success: false,
            error: 'NOT_FOUND',
            message: `${resourceType} tidak ditemukan`
          });
        }

        // Check ownership
        const isOwner = PermissionService.isOwner(
          req.user, 
          resourceType, 
          resource
        );

        // Admin can access any resource
        if (req.user.role === 'admin' || isOwner) {
          req.resource = resource;
          return next();
        }

        // Check if user has permission to access any resource of this type
        if (PermissionService.hasPermission(req.user, [`read:${resourceType}`])) {
          req.resource = resource;
          return next();
        }

        logger.warn('Authorization failed - not owner', {
          userId: req.user.id,
          resourceType,
          resourceId,
          path: req.path
        });

        return res.status(403).json({
          success: false,
          error: 'FORBIDDEN',
          message: 'Anda tidak memiliki akses ke resource ini'
        });

      } catch (error) {
        next(error);
      }
    };
  }

  /**
   * Rate limit untuk authentication attempts
   */
  authRateLimiter() {
    const attempts = new Map();
    
    return (req, res, next) => {
      const identifier = req.body.email || req.ip;
      const now = Date.now();
      const windowMs = 15 * 60 * 1000; // 15 minutes
      const maxAttempts = 5;

      const userAttempts = attempts.get(identifier) || [];
      const recentAttempts = userAttempts.filter(
        time => now - time < windowMs
      );

      if (recentAttempts.length >= maxAttempts) {
        const oldestAttempt = recentAttempts[0];
        const resetTime = Math.ceil(
          (oldestAttempt + windowMs - now) / 1000
        );

        return res.status(429).json({
          success: false,
          error: 'RATE_LIMIT_EXCEEDED',
          message: 'Terlalu banyak percobaan login. Coba lagi nanti',
          retryAfter: resetTime
        });
      }

      recentAttempts.push(now);
      attempts.set(identifier, recentAttempts);

      next();
    };
  }

  /**
   * ============= HELPER METHODS =============
   */

  /**
   * Extract token from request
   */
  extractToken(req) {
    // Check Authorization header
    const authHeader = req.headers.authorization;
    if (authHeader && authHeader.startsWith('Bearer ')) {
      return authHeader.split(' ')[1];
    }

    // Check query parameter
    if (req.query.token) {
      return req.query.token;
    }

    // Check cookie
    if (req.cookies && req.cookies.token) {
      return req.cookies.token;
    }

    return null;
  }

  /**
   * Get resource from database (simulasi)
   */
  async getResource(resourceType, id) {
    const idNum = parseInt(id);
    
    switch (resourceType) {
      case 'post':
        // Simulasi post
        return {
          id: 1,
          title: 'Test Post',
          authorId: 1,
          userId: 1
        };
      case 'comment':
        return {
          id: 1,
          content: 'Test Comment',
          userId: 1
        };
      case 'user':
        return {
          id: idNum,
          userId: idNum
        };
      default:
        return null;
    }
  }
}

module.exports = new AuthMiddleware();

F. Controllers untuk Authentication

src/controllers/auth.controller.js

javascript
/**
 * AUTH CONTROLLER
 * Handle HTTP requests untuk authentication & authorization
 */

const AuthService = require('../services/auth.service');
const { users } = require('../models/User.model');
const logger = require('../utils/logger');

class AuthController {
  /**
   * POST /api/auth/register
   * Register user baru
   */
  async register(req, res, next) {
    try {
      const result = await AuthService.register(req.body);

      res.status(201).json({
        success: true,
        message: 'Registrasi berhasil! Silakan cek email untuk verifikasi.',
        data: result
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /api/auth/login
   * Login user
   */
  async login(req, res, next) {
    try {
      const { email, password } = req.body;
      
      // Get device info for logging
      const deviceInfo = {
        ip: req.ip || req.connection.remoteAddress,
        userAgent: req.headers['user-agent'],
        platform: req.headers['sec-ch-ua-platform']
      };

      const result = await AuthService.login(email, password, deviceInfo);

      // Set refresh token in HTTP-only cookie
      res.cookie('refreshToken', result.tokens.refreshToken, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict',
        maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
      });

      res.status(200).json({
        success: true,
        message: 'Login berhasil',
        data: {
          user: result.user,
          tokens: {
            accessToken: result.tokens.accessToken,
            expiresIn: result.tokens.expiresIn
          }
        }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /api/auth/refresh
   * Refresh access token
   */
  async refreshToken(req, res, next) {
    try {
      const refreshToken = req.body.refreshToken || req.cookies.refreshToken;
      
      const result = await AuthService.refreshToken(refreshToken);

      // Update refresh token cookie
      res.cookie('refreshToken', result.tokens.refreshToken, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict',
        maxAge: 7 * 24 * 60 * 60 * 1000
      });

      res.status(200).json({
        success: true,
        message: 'Token refreshed',
        data: {
          accessToken: result.tokens.accessToken,
          expiresIn: result.tokens.expiresIn
        }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /api/auth/logout
   * Logout user
   */
  async logout(req, res, next) {
    try {
      const refreshToken = req.body.refreshToken || req.cookies.refreshToken;
      
      await AuthService.logout(req.user?.id, refreshToken);

      // Clear refresh token cookie
      res.clearCookie('refreshToken');

      res.status(200).json({
        success: true,
        message: 'Logout berhasil'
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /api/auth/me
   * Get current user profile
   */
  async getProfile(req, res, next) {
    try {
      res.status(200).json({
        success: true,
        data: {
          user: req.user.toJSON(),
          permissions: req.user.permissions || []
        }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /api/auth/verify-email
   * Verify email address
   */
  async verifyEmail(req, res, next) {
    try {
      const { token } = req.query;
      
      // Find user by token
      const hashedToken = require('crypto')
        .createHash('sha256')
        .update(token)
        .digest('hex');

      const user = users.find(u => u.emailVerificationToken === hashedToken);

      if (!user) {
        return res.status(400).json({
          success: false,
          error: 'INVALID_TOKEN',
          message: 'Token verifikasi tidak valid atau sudah kadaluarsa'
        });
      }

      // Verify email
      user.isEmailVerified = true;
      user.emailVerificationToken = null;

      logger.info(`Email verified: ${user.email}`, { userId: user.id });

      res.status(200).json({
        success: true,
        message: 'Email berhasil diverifikasi! Silakan login.'
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /api/auth/forgot-password
   * Request password reset
   */
  async forgotPassword(req, res, next) {
    try {
      const { email } = req.body;
      
      const user = users.find(u => u.email === email.toLowerCase());

      if (user) {
        const resetToken = user.generatePasswordResetToken();
        await AuthService.sendPasswordResetEmail(email, resetToken);
      }

      // Always return success to prevent email enumeration
      res.status(200).json({
        success: true,
        message: 'Jika email terdaftar, link reset password akan dikirim'
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /api/auth/reset-password
   * Reset password with token
   */
  async resetPassword(req, res, next) {
    try {
      const { token, newPassword } = req.body;
      
      // Validate password strength
      const passwordErrors = AuthService.validatePasswordStrength(newPassword);
      if (passwordErrors.length > 0) {
        return res.status(400).json({
          success: false,
          error: 'VALIDATION_ERROR',
          message: 'Password tidak memenuhi kriteria',
          errors: passwordErrors
        });
      }

      // Find user by reset token
      const hashedToken = require('crypto')
        .createHash('sha256')
        .update(token)
        .digest('hex');

      const user = users.find(u => 
        u.passwordResetToken === hashedToken && 
        u.passwordResetExpires > Date.now()
      );

      if (!user) {
        return res.status(400).json({
          success: false,
          error: 'INVALID_TOKEN',
          message: 'Token reset password tidak valid atau sudah kadaluarsa'
        });
      }

      // Update password
      user.password = await User.hashPassword(newPassword);
      user.passwordResetToken = null;
      user.passwordResetExpires = null;

      // Revoke all refresh tokens for security
      user.refreshTokens = [];

      logger.info(`Password reset successful: ${user.email}`, {
        userId: user.id
      });

      res.status(200).json({
        success: true,
        message: 'Password berhasil direset! Silakan login dengan password baru.'
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /api/auth/permissions
   * Get user permissions
   */
  async getPermissions(req, res, next) {
    try {
      const permissions = req.user.permissions || [];

      res.status(200).json({
        success: true,
        data: {
          role: req.user.role,
          permissions,
          isAdmin: req.user.role === 'admin',
          can: (permission) => permissions.includes(permission)
        }
      });
    } catch (error) {
      next(error);
    }
  }
}

module.exports = new AuthController();

G. Routes untuk Authentication

src/routes/auth.routes.js

javascript
/**
 * AUTH ROUTES
 * Definisi endpoint untuk authentication & authorization
 */

const express = require('express');
const router = express.Router();
const AuthController = require('../controllers/auth.controller');
const AuthMiddleware = require('../middleware/auth.middleware');
const { validate } = require('../middleware/validation.middleware');

/**
 * @swagger
 * tags:
 *   name: Authentication
 *   description: User authentication and authorization
 */

// ============= PUBLIC ROUTES =============

/**
 * @swagger
 * /auth/register:
 *   post:
 *     summary: Register new user
 *     tags: [Authentication]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - email
 *               - password
 *               - name
 *             properties:
 *               email:
 *                 type: string
 *                 format: email
 *               password:
 *                 type: string
 *                 format: password
 *               name:
 *                 type: string
 *               role:
 *                 type: string
 *                 enum: [user, moderator]
 *     responses:
 *       201:
 *         description: Registration successful
 *       400:
 *         description: Validation error
 *       409:
 *         description: Email already exists
 */
router.post('/register', 
  AuthMiddleware.authRateLimiter(),
  validate('register'),
  AuthController.register
);

/**
 * @swagger
 * /auth/login:
 *   post:
 *     summary: Login user
 *     tags: [Authentication]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - email
 *               - password
 *             properties:
 *               email:
 *                 type: string
 *                 format: email
 *               password:
 *                 type: string
 *                 format: password
 *     responses:
 *       200:
 *         description: Login successful
 *       401:
 *         description: Invalid credentials
 *       403:
 *         description: Email not verified / Account inactive
 *       423:
 *         description: Account locked
 */
router.post('/login',
  AuthMiddleware.authRateLimiter(),
  validate('login'),
  AuthController.login
);

/**
 * @swagger
 * /auth/refresh:
 *   post:
 *     summary: Refresh access token
 *     tags: [Authentication]
 *     requestBody:
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               refreshToken:
 *                 type: string
 *     responses:
 *       200:
 *         description: Token refreshed
 *       401:
 *         description: Invalid/expired refresh token
 */
router.post('/refresh',
  AuthController.refreshToken
);

/**
 * @swagger
 * /auth/verify-email:
 *   get:
 *     summary: Verify email address
 *     tags: [Authentication]
 *     parameters:
 *       - in: query
 *         name: token
 *         schema:
 *           type: string
 *         required: true
 *         description: Email verification token
 *     responses:
 *       200:
 *         description: Email verified successfully
 *       400:
 *         description: Invalid/expired token
 */
router.get('/verify-email',
  AuthController.verifyEmail
);

/**
 * @swagger
 * /auth/forgot-password:
 *   post:
 *     summary: Request password reset
 *     tags: [Authentication]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - email
 *             properties:
 *               email:
 *                 type: string
 *                 format: email
 *     responses:
 *       200:
 *         description: Reset link sent (if email exists)
 */
router.post('/forgot-password',
  validate('forgotPassword'),
  AuthController.forgotPassword
);

/**
 * @swagger
 * /auth/reset-password:
 *   post:
 *     summary: Reset password with token
 *     tags: [Authentication]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - token
 *               - newPassword
 *             properties:
 *               token:
 *                 type: string
 *               newPassword:
 *                 type: string
 *                 format: password
 *     responses:
 *       200:
 *         description: Password reset successful
 *       400:
 *         description: Invalid/expired token or weak password
 */
router.post('/reset-password',
  validate('resetPassword'),
  AuthController.resetPassword
);

// ============= PROTECTED ROUTES =============

/**
 * @swagger
 * /auth/me:
 *   get:
 *     summary: Get current user profile
 *     tags: [Authentication]
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: User profile retrieved
 *       401:
 *         description: Unauthorized
 */
router.get('/me',
  AuthMiddleware.authenticate(),
  AuthController.getProfile
);

/**
 * @swagger
 * /auth/logout:
 *   post:
 *     summary: Logout user
 *     tags: [Authentication]
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: Logout successful
 */
router.post('/logout',
  AuthMiddleware.authenticate(),
  AuthController.logout
);

/**
 * @swagger
 * /auth/permissions:
 *   get:
 *     summary: Get user permissions
 *     tags: [Authentication]
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: User permissions retrieved
 */
router.get('/permissions',
  AuthMiddleware.authenticate(),
  AuthController.getPermissions
);

// ============= ADMIN ROUTES =============

/**
 * @swagger
 * /auth/admin/users:
 *   get:
 *     summary: Get all users (admin only)
 *     tags: [Authentication]
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: Users retrieved
 *       403:
 *         description: Forbidden (requires admin role)
 */
router.get('/admin/users',
  AuthMiddleware.authenticate(),
  AuthMiddleware.authorize('admin'),
  (req, res) => {
    const { users } = require('../models/User.model');
    res.json({
      success: true,
      data: users.map(u => u.toJSON())
    });
  }
);

module.exports = router;

3.5 JWT Token Structure & Flow

javascript
/**
 * JWT (JSON Web Token) STRUCTURE
 * 
 * 1. HEADER (Algorithm & Token Type)
 * {
 *   "alg": "HS256",
 *   "typ": "JWT"
 * }
 * 
 * 2. PAYLOAD (Data)
 * {
 *   "sub": 1,                    // Subject (user ID)
 *   "email": "user@example.com", // User email
 *   "role": "admin",            // User role
 *   "permissions": [...],       // User permissions
 *   "iss": "auth-system",       // Issuer
 *   "aud": "auth-client",       // Audience
 *   "iat": 1709462400,         // Issued at
 *   "exp": 1709463300,         // Expiration
 *   "jti": "uuid-v4"           // JWT ID (optional)
 * }
 * 
 * 3. SIGNATURE
 * HMACSHA256(
 *   base64UrlEncode(header) + "." +
 *   base64UrlEncode(payload),
 *   secret
 * )
 * 
 * FINAL JWT:
 * eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
 * eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
 * SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
 */

/**
 * JWT AUTHENTICATION FLOW
 * 
 * ┌─────────┐         ┌─────────┐         ┌─────────┐
 * │  CLIENT │         │ SERVER  │         │ DATABASE│
 * └────┬────┘         └────┬────┘         └────┬────┘
 *      │                   │                   │
 *      │   POST /login     │                   │
 *      │──────────────────>│                   │
 *      │   {email,pass}    │                   │
 *      │                   │                   │
 *      │                   │   Verify User     │
 *      │                   │──────────────────>│
 *      │                   │                   │
 *      │                   │   User Found      │
 *      │                   │<──────────────────│
 *      │                   │                   │
 *      │                   │ Generate JWT      │
 *      │                   │   (Sign)          │
 *      │                   │                   │
 *      │   200 OK + JWT    │                   │
 *      │<──────────────────│                   │
 *      │                   │                   │
 *      │                   │                   │
 *      │   GET /profile    │                   │
 *      │──────────────────>│                   │
 *      │   Auth: Bearer JWT│                   │
 *      │                   │                   │
 *      │                   │ Verify JWT        │
 *      │                   │   (Decode + Check)│
 *      │                   │                   │
 *      │                   │ Get User by ID    │
 *      │                   │──────────────────>│
 *      │                   │                   │
 *      │                   │ User Data        │
 *      │                   │<──────────────────│
 *      │                   │                   │
 *      │   200 OK + Data   │                   │
 *      │<──────────────────│                   │
 * ┌────┴────┐         ┌────┴────┐         ┌────┴────┐
 * │  CLIENT │         │ SERVER  │         │ DATABASE│
 * └─────────┘         └─────────┘         └─────────┘
 */

3.6 Session vs JWT vs OAuth

javascript
/**
 * COMPARISON: AUTHENTICATION METHODS
 */

const authComparison = {
  /**
   * 1. SESSION-BASED AUTHENTICATION
   * Pros: Simple, server control, easy revocation
   * Cons: Stateful, not scalable horizontally
   */
  session: {
    how: "Server menyimpan session di memory/database",
    flow: `
      1. Client login → Server create session
      2. Server return session ID in cookie
      3. Client sends cookie with every request
      4. Server validates session from store
    `,
    pros: [
      "Instant revocation (delete session)",
      "No token exposure in JavaScript",
      "Works with legacy systems"
    ],
    cons: [
      "Stateful - server must store sessions",
      "Hard to scale horizontally",
      "Session fixation attacks",
      "Memory intensive"
    ],
    bestFor: [
      "Traditional web apps",
      "Small to medium applications",
      "Internal tools",
      "Systems requiring instant logout"
    ]
  },

  /**
   * 2. JWT (JSON Web Token) BASED
   * Pros: Stateless, scalable, cross-domain
   * Cons: Cannot revoke, token size, storage concerns
   */
  jwt: {
    how: "Server menandatangani token, client menyimpan",
    flow: `
      1. Client login → Server generate JWT
      2. Server return JWT to client
      3. Client stores JWT (localStorage/memory)
      4. Client sends JWT in Authorization header
      5. Server verifies signature, no DB lookup
    `,
    pros: [
      "Stateless - no session storage",
      "Horizontally scalable",
      "Cross-domain / CORS friendly",
      "Can include custom data",
      "Mobile app friendly"
    ],
    cons: [
      "Cannot revoke (until expiry)",
      "Token size grows with claims",
      "Storage concerns (XSS)",
      "No built-in refresh mechanism"
    ],
    bestFor: [
      "REST APIs",
      "Microservices",
      "Mobile applications",
      "SPA (Single Page Apps)",
      "Stateless architectures"
    ]
  },

  /**
   * 3. OAUTH 2.0 / OPENID CONNECT
   * Pros: Delegated auth, 3rd party login, industry standard
   * Cons: Complex, requires redirects, multiple flows
   */
  oauth: {
    how: "Delegated authorization via third-party",
    flow: `
      1. Client requests authorization
      2. Redirect to auth provider
      3. User logs in with provider
      4. Provider returns authorization code
      5. Exchange code for access token
      6. Access resources with token
    `,
    pros: [
      "No password storage needed",
      "Users trust familiar providers",
      "Rich permission scoping",
      "Industry standard",
      "Social login (Google, Facebook)"
    ],
    cons: [
      "Complex implementation",
      "Requires HTTPS redirects",
      "Third-party dependency",
      "Multiple flow types"
    ],
    bestFor: [
      "Third-party authentication",
      "Social login features",
      "Enterprise SSO",
      "Mobile apps with social login"
    ]
  },

  /**
   * 4. API KEY AUTHENTICATION
   * Pros: Simple, machine-to-machine
   * Cons: Less secure, no user context
   */
  apiKey: {
    how: "Pre-shared key in headers",
    pros: [
      "Very simple to implement",
      "No user interaction needed",
      "Good for service accounts"
    ],
    cons: [
      "No user context",
      "Key rotation difficult",
      "Less secure (static keys)",
      "No expiration by default"
    ],
    bestFor: [
      "Public APIs with rate limiting",
      "Service-to-service communication",
      "Developer APIs",
      "IoT devices"
    ]
  }
};

/**
 * CHOOSING THE RIGHT AUTHENTICATION
 */
function chooseAuthMethod(req, res) {
  const factors = {
    needStateless: true,        // Scale horizontally?
    needRevocation: false,      // Need instant logout?
    isMobileApp: true,         // Mobile or web?
    thirdPartyLogin: false,    // Use Google/Facebook?
    machineToMachine: false    // API for other services?
  };

  if (factors.machineToMachine) {
    return 'API Key + JWT for service accounts';
  }
  
  if (factors.thirdPartyLogin) {
    return 'OAuth 2.0 + JWT for your own sessions';
  }
  
  if (factors.needStateless && !factors.needRevocation) {
    return 'JWT with short expiry + refresh tokens';
  }
  
  if (factors.needRevocation && !factors.needStateless) {
    return 'Session-based authentication';
  }
  
  // Hybrid approach
  return 'JWT for API, Session for web, OAuth for social';
}

3.7 Role & Permission Matrix

javascript
/**
 * ROLE-BASED ACCESS CONTROL (RBAC) MATRIX
 */

const rbacMatrix = {
  // Role definitions
  roles: {
    admin: {
      level: 100,
      description: 'Full system access',
      permissions: ['*']
    },
    
    moderator: {
      level: 50,
      description: 'Content management',
      permissions: [
        'read:posts',
        'create:posts',
        'update:posts',
        'delete:posts',
        'read:comments',
        'delete:comments',
        'read:users',
        'update:users',
        'read:analytics'
      ]
    },
    
    user: {
      level: 10,
      description: 'Regular user',
      permissions: [
        'read:profile',
        'update:profile',
        'read:posts',
        'create:posts',
        'update:own_posts',
        'delete:own_posts',
        'create:comments',
        'update:own_comments',
        'delete:own_comments'
      ]
    },
    
    guest: {
      level: 0,
      description: 'Unauthenticated',
      permissions: [
        'read:posts',
        'read:public_profile'
      ]
    }
  },

  // Permission categories
  categories: {
    profile: {
      read: 'View profile information',
      update: 'Update own profile',
      delete: 'Delete account'
    },
    posts: {
      read: 'View posts',
      create: 'Create new posts',
      update: 'Update any post',
      delete: 'Delete any post',
      'update:own': 'Update own posts',
      'delete:own': 'Delete own posts'
    },
    comments: {
      read: 'View comments',
      create: 'Create comments',
      update: 'Update any comment',
      delete: 'Delete any comment',
      'update:own': 'Update own comments',
      'delete:own': 'Delete own comments'
    },
    users: {
      read: 'View users list',
      update: 'Update users',
      delete: 'Delete users'
    },
    admin: {
      access: 'Access admin panel',
      config: 'Modify system config',
      logs: 'View system logs'
    }
  },

  // Helper: Check if user has permission
  checkPermission(user, requiredPermission) {
    if (!user || !user.role) return false;
    
    const role = this.roles[user.role];
    if (!role) return false;
    
    // Admin has all permissions
    if (role.permissions.includes('*')) return true;
    
    // Check exact permission
    if (role.permissions.includes(requiredPermission)) return true;
    
    // Check wildcard permissions (e.g., 'read:*')
    const [action, resource] = requiredPermission.split(':');
    
    return role.permissions.some(p => {
      const [pAction, pResource] = p.split(':');
      
      if (pAction === action && pResource === '*') return true;
      if (pAction === '*' && pResource === resource) return true;
      if (p === `${action}:${resource}`) return true;
      
      return false;
    });
  },

  // Helper: Get all permissions for role
  getPermissionsForRole(roleName) {
    const role = this.roles[roleName];
    return role ? role.permissions : [];
  }
};

/**
 * PERMISSION-BASED ACCESS CONTROL (PBAC) MATRIX
 * More granular than RBAC, allows custom permissions per user
 */

const pbacMatrix = {
  // Permission categories
  resources: {
    posts: ['create', 'read', 'update', 'delete'],
    comments: ['create', 'read', 'update', 'delete'],
    users: ['read', 'update', 'delete'],
    profile: ['read', 'update'],
    analytics: ['read'],
    settings: ['read', 'update']
  },

  // Permission strings format: "action:resource"
  // Examples:
  // - 'create:posts'  → Create posts
  // - 'read:users'    → Read users
  // - 'update:profile' → Update profile
  // - 'delete:comments' → Delete comments
  // - 'read:*'        → Read all resources
  // - '*:posts'       → All actions on posts
  // - '*'             → All permissions

  // Helper: Validate permission format
  isValidPermission(permission) {
    if (permission === '*') return true;
    
    const parts = permission.split(':');
    if (parts.length !== 2) return false;
    
    const [action, resource] = parts;
    
    if (action === '*') return true;
    if (resource === '*') return true;
    
    return this.resources[resource] && 
           this.resources[resource].includes(action);
  },

  // Helper: Generate permission from action and resource
  generatePermission(action, resource) {
    if (action === 'all' && resource === 'all') return '*';
    if (action === 'all') return `*:${resource}`;
    if (resource === 'all') return `${action}:*`;
    return `${action}:${resource}`;
  }
};

3.8 Security Best Practices untuk Authentication

javascript
/**
 * AUTHENTICATION SECURITY BEST PRACTICES
 */

const securityBestPractices = {
  /**
   * 1. PASSWORD STORAGE
   */
  passwordStorage: {
    do: [
      "Use bcrypt, scrypt, or argon2 for hashing",
      "Salt rounds: 10-12 (bcrypt)",
      "Minimum password length: 8 characters",
      "Enforce password complexity"
    ],
    dont: [
      "Never store plain text passwords",
      "Don't use MD5, SHA1, or SHA256 without salt",
      "Don't limit password length excessively",
      "Don't truncate passwords"
    ],
    example: `
      const bcrypt = require('bcryptjs');
      const saltRounds = 10;
      
      // Hash password
      const hash = await bcrypt.hash(password, saltRounds);
      
      // Verify password
      const isValid = await bcrypt.compare(password, hash);
    `
  },

  /**
   * 2. JWT SECURITY
   */
  jwtSecurity: {
    do: [
      "Use strong secret keys (min 32 chars)",
      "Set short expiration times (15m-1h)",
      "Use refresh tokens for long-lived sessions",
      "Store JWT in HTTP-only cookies",
      "Include issuer and audience claims",
      "Use HTTPS in production"
    ],
    dont: [
      "Don't store sensitive data in JWT payload",
      "Don't accept tokens without expiration",
      "Don't use weak signing algorithms (HS256 is fine)",
      "Don't store JWT in localStorage (XSS risk)",
      "Don't log JWT tokens"
    ],
    example: `
      const jwt = require('jsonwebtoken');
      
      // Generate token
      const token = jwt.sign(
        { 
          sub: user.id,
          role: user.role,
          iss: 'myapp',
          aud: 'myapp-client'
        },
        process.env.JWT_SECRET,
        { expiresIn: '15m' }
      );
      
      // Verify token
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
    `
  },

  /**
   * 3. RATE LIMITING
   */
  rateLimiting: {
    do: [
      "Limit login attempts (5 per 15 minutes)",
      "Limit registration (3 per hour per IP)",
      "Limit password reset requests",
      "Use exponential backoff",
      "Implement account lockout after failed attempts"
    ],
    dont: [
      "Don't rely on client-side rate limiting",
      "Don't use the same limit for all endpoints",
      "Don't forget to count failed attempts"
    ],
    example: `
      const rateLimit = require('express-rate-limit');
      
      const loginLimiter = rateLimit({
        windowMs: 15 * 60 * 1000, // 15 minutes
        max: 5, // 5 attempts
        skipSuccessfulRequests: true,
        message: 'Too many login attempts'
      });
      
      app.post('/login', loginLimiter, loginHandler);
    `
  },

  /**
   * 4. ACCOUNT RECOVERY
   */
  accountRecovery: {
    do: [
      "Send password reset via email only",
      "Use short-lived tokens (1 hour)",
      "Invalidate token after use",
      "Notify user of password changes",
      "Use secure token generation (crypto.randomBytes)"
    ],
    dont: [
      "Don't email passwords",
      "Don't use sequential reset tokens",
      "Don't allow password reset without email verification",
      "Don't reveal if email exists"
    ],
    example: `
      // Generate secure reset token
      const crypto = require('crypto');
      
      const token = crypto.randomBytes(32).toString('hex');
      const hashedToken = crypto
        .createHash('sha256')
        .update(token)
        .digest('hex');
      
      // Store hashed token, send raw token via email
    `
  },

  /**
   * 5. CORS & COOKIES
   */
  corsAndCookies: {
    do: [
      "Set CORS properly (whitelist origins)",
      "Use HTTP-only cookies for refresh tokens",
      "Set Secure flag in production",
      "Set SameSite=Strict or SameSite=Lax",
      "Use CSRF tokens for state-changing operations"
    ],
    dont: [
      "Don't use wildcard CORS with credentials",
      "Don't expose refresh tokens to JavaScript",
      "Don't set cookies without secure flags"
    ],
    example: `
      // HTTP-only cookie
      res.cookie('refreshToken', token, {
        httpOnly: true,      // Not accessible via JS
        secure: true,        // HTTPS only
        sameSite: 'strict',  // CSRF protection
        maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
      });
    `
  },

  /**
   * 6. AUTHORIZATION
   */
  authorization: {
    do: [
      "Check permissions on every request",
      "Use principle of least privilege",
      "Implement role-based access control",
      "Verify resource ownership",
      "Log authorization failures"
    ],
    dont: [
      "Don't rely only on frontend authorization",
      "Don't assume user has permission without checking",
      "Don't use insecure direct object references"
    ],
    example: `
      // Check ownership
      if (req.user.role !== 'admin' && 
          resource.userId !== req.user.id) {
        return res.status(403).json({
          error: 'FORBIDDEN',
          message: 'Not authorized'
        });
      }
    `
  }
};

3.9 Testing Authentication & Authorization

tests/auth/authentication.test.js

javascript
/**
 * TESTING AUTHENTICATION & AUTHORIZATION
 */

const request = require('supertest');
const app = require('../../src/app');
const { users } = require('../../src/models/User.model');

describe('Authentication System', () => {
  beforeEach(() => {
    // Reset users or use test database
  });

  describe('POST /api/auth/register', () => {
    it('should register new user successfully', async () => {
      const response = await request(app)
        .post('/api/auth/register')
        .send({
          email: 'test@example.com',
          password: 'Test123!',
          name: 'Test User'
        });

      expect(response.statusCode).toBe(201);
      expect(response.body.success).toBe(true);
      expect(response.body.data.user).toHaveProperty('id');
      expect(response.body.data.user.email).toBe('test@example.com');
      expect(response.body.data.tokens).toHaveProperty('accessToken');
    });

    it('should return 409 if email already exists', async () => {
      await request(app)
        .post('/api/auth/register')
        .send({
          email: 'duplicate@example.com',
          password: 'Test123!',
          name: 'Test User'
        });

      const response = await request(app)
        .post('/api/auth/register')
        .send({
          email: 'duplicate@example.com',
          password: 'Test123!',
          name: 'Another User'
        });

      expect(response.statusCode).toBe(409);
      expect(response.body.error).toBe('EMAIL_EXISTS');
    });

    it('should validate password strength', async () => {
      const response = await request(app)
        .post('/api/auth/register')
        .send({
          email: 'weak@example.com',
          password: 'weak',
          name: 'Weak Password'
        });

      expect(response.statusCode).toBe(400);
      expect(response.body.error).toBe('VALIDATION_ERROR');
      expect(response.body.errors).toBeDefined();
    });
  });

  describe('POST /api/auth/login', () => {
    it('should login successfully with valid credentials', async () => {
      const response = await request(app)
        .post('/api/auth/login')
        .send({
          email: 'admin@system.com',
          password: 'Admin123!'
        });

      expect(response.statusCode).toBe(200);
      expect(response.body.data).toHaveProperty('user');
      expect(response.body.data).toHaveProperty('tokens');
      expect(response.headers['set-cookie']).toBeDefined();
    });

    it('should return 401 with invalid credentials', async () => {
      const response = await request(app)
        .post('/api/auth/login')
        .send({
          email: 'admin@system.com',
          password: 'wrongpassword'
        });

      expect(response.statusCode).toBe(401);
      expect(response.body.error).toBe('INVALID_CREDENTIALS');
    });

    it('should lock account after multiple failed attempts', async () => {
      // 5 failed attempts
      for (let i = 0; i < 5; i++) {
        await request(app)
          .post('/api/auth/login')
          .send({
            email: 'admin@system.com',
            password: 'wrongpassword'
          });
      }

      const response = await request(app)
        .post('/api/auth/login')
        .send({
          email: 'admin@system.com',
          password: 'Admin123!'
        });

      expect(response.statusCode).toBe(423);
      expect(response.body.error).toBe('ACCOUNT_LOCKED');
    });
  });

  describe('Protected Routes', () => {
    let authToken;

    beforeEach(async () => {
      const loginResponse = await request(app)
        .post('/api/auth/login')
        .send({
          email: 'user@example.com',
          password: 'User123!'
        });
      
      authToken = loginResponse.body.data.tokens.accessToken;
    });

    it('should access protected route with valid token', async () => {
      const response = await request(app)
        .get('/api/auth/me')
        .set('Authorization', `Bearer ${authToken}`);

      expect(response.statusCode).toBe(200);
      expect(response.body.data.user).toBeDefined();
    });

    it('should return 401 without token', async () => {
      const response = await request(app)
        .get('/api/auth/me');

      expect(response.statusCode).toBe(401);
      expect(response.body.error).toBe('NO_TOKEN');
    });

    it('should return 401 with invalid token', async () => {
      const response = await request(app)
        .get('/api/auth/me')
        .set('Authorization', 'Bearer invalid.token.here');

      expect(response.statusCode).toBe(401);
      expect(response.body.error).toBe('INVALID_TOKEN');
    });

    it('should enforce role-based access control', async () => {
      // User trying to access admin route
      const response = await request(app)
        .get('/api/auth/admin/users')
        .set('Authorization', `Bearer ${authToken}`);

      expect(response.statusCode).toBe(403);
      expect(response.body.error).toBe('FORBIDDEN');
    });

    it('should allow admin access', async () => {
      const adminLogin = await request(app)
        .post('/api/auth/login')
        .send({
          email: 'admin@system.com',
          password: 'Admin123!'
        });

      const adminToken = adminLogin.body.data.tokens.accessToken;

      const response = await request(app)
        .get('/api/auth/admin/users')
        .set('Authorization', `Bearer ${adminToken}`);

      expect(response.statusCode).toBe(200);
      expect(response.body.data).toBeInstanceOf(Array);
    });
  });

  describe('Token Refresh', () => {
    it('should refresh access token with valid refresh token', async () => {
      const loginResponse = await request(app)
        .post('/api/auth/login')
        .send({
          email: 'user@example.com',
          password: 'User123!'
        });

      const refreshToken = loginResponse.body.data.tokens.refreshToken;

      const refreshResponse = await request(app)
        .post('/api/auth/refresh')
        .send({ refreshToken });

      expect(refreshResponse.statusCode).toBe(200);
      expect(refreshResponse.body.data).toHaveProperty('accessToken');
      expect(refreshResponse.body.data).toHaveProperty('expiresIn');
    });

    it('should return 401 with invalid refresh token', async () => {
      const response = await request(app)
        .post('/api/auth/refresh')
        .send({ refreshToken: 'invalid.token' });

      expect(response.statusCode).toBe(401);
      expect(response.body.error).toBe('INVALID_REFRESH_TOKEN');
    });
  });
});

3.10 Latihan Praktikum

Exercise 1: Social Login Integration

javascript
/**
 * TODO: Implement OAuth 2.0 dengan Google
 * - Setup Google Cloud Console
 * - Implement OAuth2 flow
 * - Link social account ke user existing
 * - Generate JWT setelah OAuth success
 */

class GoogleOAuthService {
  async getAuthorizationUrl() {
    // Implementasi
  }

  async handleCallback(code) {
    // Implementasi
  }

  async findOrCreateUser(profile) {
    // Implementasi
  }
}

Exercise 2: Two-Factor Authentication (2FA)

javascript
/**
 * TODO: Implement Two-Factor Authentication
 * - Generate QR code untuk Google Authenticator
 * - Verify TOTP code
 * - Backup codes
 * - Remember device option
 */

class TwoFactorService {
  async generateSecret(userId) {
    // Implementasi
  }

  async verifyToken(secret, token) {
    // Implementasi
  }

  async generateBackupCodes() {
    // Implementasi
  }
}

Exercise 3: Permission Management Dashboard

javascript
/**
 * TODO: Build admin dashboard untuk manage permissions
 * - CRUD roles
 * - Assign permissions to roles
 * - Assign roles to users
 * - Custom permissions per user
 */

class PermissionManager {
  async createRole(roleData) {
    // Implementasi
  }

  async assignPermission(roleId, permission) {
    // Implementasi
  }

  async getUserPermissions(userId) {
    // Implementasi
  }
}

Exercise 4: Audit Log untuk Authentication

javascript
/**
 * TODO: Implement audit logging untuk security events
 * - Log all login attempts (success/fail)
 * - Log password changes
 * - Log permission changes
 * - Log account locks/unlocks
 */

class AuthAuditLogger {
  async logLoginAttempt(email, success, ip, userAgent) {
    // Implementasi
  }

  async logPasswordChange(userId, changedBy) {
    // Implementasi
  }

  async getSecurityAudit(userId) {
    // Implementasi
  }
}

4. Daftar Pustaka

  1. Medium (2024). Building an Authentication and Authorization API with Express.js.   https://medium.com/@pirson
  2. Karisma Academy (2026). JWT Authentication di Express.js untuk Pemulahttps://blog.karismaacademy.com
  3. ApiDog (2025). Autentikasi Node.js Express: Konsep, Metode, dan Contoh. https://apidog.com
  4. DevTo (2025). OAuth Authentication: Google, Facebook, Github Login via Node JS (Banckend)https://dev.to/uniyalmanas
  5. Medium (2023). Understanding Hashing, Encoding, and Encryption in Express.js. https://medium.com

Posting Komentar

0 Komentar