{"id":4444,"date":"2025-11-11T08:21:30","date_gmt":"2025-11-11T01:21:30","guid":{"rendered":"https:\/\/www.jagowebdesign.com\/website\/?p=4444"},"modified":"2025-11-11T08:21:30","modified_gmt":"2025-11-11T01:21:30","slug":"cybersecurity-web-application-security-best-practices-complete-guide","status":"publish","type":"post","link":"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/","title":{"rendered":"Cybersecurity: Web Application Security Best Practices Complete Guide"},"content":{"rendered":"<p>Di era digital di mana cyber threats semakin sophisticated, web application security menjadi prioritas utama untuk developer dan bisnis. Data breaches dapat menyebabkan kerugian finansial yang signifikan, kehilangan kepercayaan customer, dan konsekuensi legal yang serius. Artikel ini akan memberikan comprehensive guide tentang web application security best practices di tahun 2025.<\/p>\n<p>Understanding Modern Cybersecurity Landscape<\/p>\n<p>1. Current Threat Landscape di 2025<br \/>\n\u2022 Ransomware Evolution: More sophisticated encryption methods dan targeted attacks<br \/>\n\u2022 AI-Powered Attacks: Machine learning untuk automated vulnerability scanning<br \/>\n\u2022 Supply Chain Attacks: Compromise melalui third-party dependencies<br \/>\n\u2022 API Security Threats: Increased API attack vectors dan data exposure<br \/>\n\u2022 Cloud Security Challenges: Multi-cloud environment complexities<\/p>\n<p>2. Indonesia Cybersecurity Context<br \/>\n\u2022 Regulatory Compliance: PDPA (Personal Data Protection Act) implementation<br \/>\n\u2022 Financial Sector: BI (Bank Indonesia) security regulations<br \/>\n\u2022 Government Sector: SINAP dan national cybersecurity frameworks<br \/>\n\u2022 Startup Ecosystem: Growing security awareness dalam tech community<\/p>\n<p>3. Attack Vectors Evolution<br \/>\n\u2022 Traditional Web Vulnerabilities: SQL injection, XSS, CSRF masih relevan<br \/>\n\u2022 Modern Attack Patterns: API abuse, business logic flaws, zero-day exploits<br \/>\n\u2022 Social Engineering: Phishing, vishing, dan social media attacks<br \/>\n\u2022 Infrastructure Attacks: DDoS, DNS attacks, network intrusion<\/p>\n<p>OWASP Top 10 2021 Updated Implementation<\/p>\n<p>1. A01: Broken Access Control<br \/>\n&#8220;`javascript<br \/>\n\/\/ Broken Access Control Examples dan Solutions<\/p>\n<p>\/\/ VULNERABLE: No authorization check<br \/>\napp.get(&#8216;\/api\/users\/:id&#8217;, async (req, res) =&gt; {<br \/>\n  const user = await User.findById(req.params.id);<br \/>\n  res.json(user); \/\/ Any user can access any user data!<br \/>\n});<\/p>\n<p>\/\/ SECURE: Proper authorization check<br \/>\napp.get(&#8216;\/api\/users\/:id&#8217;, async (req, res) =&gt; {<br \/>\n  \/\/ Check if user has permission to access resource<br \/>\n  if (req.user.id !== req.params.id &amp;&amp; !req.user.isAdmin) {<br \/>\n    return res.status(403).json({ error: &#8216;Access denied&#8217; });<br \/>\n  }<\/p>\n<p>  const user = await User.findById(req.params.id);<br \/>\n  if (!user) {<br \/>\n    return res.status(404).json({ error: &#8216;User not found&#8217; });<br \/>\n  }<\/p>\n<p>  res.json(user);<br \/>\n});<\/p>\n<p>\/\/ Role-based access control (RBAC) implementation<br \/>\nclass AuthorizationMiddleware {<br \/>\n  constructor() {<br \/>\n    this.roles = {<br \/>\n      admin: [&#8216;read&#8217;, &#8216;write&#8217;, &#8216;delete&#8217;, &#8216;manage_users&#8217;],<br \/>\n      editor: [&#8216;read&#8217;, &#8216;write&#8217;],<br \/>\n      viewer: [&#8216;read&#8217;]<br \/>\n    };<br \/>\n  }<\/p>\n<p>  requirePermission(permission) {<br \/>\n    return (req, res, next) =&gt; {<br \/>\n      const userRole = req.user.role;<br \/>\n      const userPermissions = this.roles[userRole] || [];<\/p>\n<p>      if (!userPermissions.includes(permission)) {<br \/>\n        return res.status(403).json({<br \/>\n          error: &#8216;Insufficient permissions&#8217;,<br \/>\n          required: permission,<br \/>\n          current: userRole<br \/>\n        });<br \/>\n      }<\/p>\n<p>      next();<br \/>\n    };<br \/>\n  }<\/p>\n<p>  requireOwnership(resourceType) {<br \/>\n    return async (req, res, next) =&gt; {<br \/>\n      try {<br \/>\n        const resourceId = req.params.id;<br \/>\n        const userId = req.user.id;<\/p>\n<p>        let resource;<br \/>\n        switch (resourceType) {<br \/>\n          case &#8216;post&#8217;:<br \/>\n            resource = await Post.findById(resourceId);<br \/>\n            break;<br \/>\n          case &#8216;comment&#8217;:<br \/>\n            resource = await Comment.findById(resourceId);<br \/>\n            break;<br \/>\n          default:<br \/>\n            return res.status(400).json({ error: &#8216;Invalid resource type&#8217; });<br \/>\n        }<\/p>\n<p>        if (!resource) {<br \/>\n          return res.status(404).json({ error: &#8216;Resource not found&#8217; });<br \/>\n        }<\/p>\n<p>        \/\/ Admin can access all resources<br \/>\n        if (req.user.role === &#8216;admin&#8217;) {<br \/>\n          req.resource = resource;<br \/>\n          return next();<br \/>\n        }<\/p>\n<p>        \/\/ Check ownership<br \/>\n        if (resource.author.toString() !== userId) {<br \/>\n          return res.status(403).json({ error: &#8216;Access denied&#8217; });<br \/>\n        }<\/p>\n<p>        req.resource = resource;<br \/>\n        next();<br \/>\n      } catch (error) {<br \/>\n        res.status(500).json({ error: &#8216;Authorization check failed&#8217; });<br \/>\n      }<br \/>\n    };<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ Usage<br \/>\nconst authz = new AuthorizationMiddleware();<br \/>\napp.get(&#8216;\/api\/posts\/:id&#8217;, authenticateToken, authz.requireOwnership(&#8216;post&#8217;), getPost);<br \/>\napp.delete(&#8216;\/api\/posts\/:id&#8217;, authenticateToken, authz.requirePermission(&#8216;delete&#8217;), authz.requireOwnership(&#8216;post&#8217;), deletePost);<br \/>\n&#8220;`<\/p>\n<p>2. A02: Cryptographic Failures<br \/>\n&#8220;`javascript<br \/>\n\/\/ Secure Cryptographic Implementation<\/p>\n<p>import crypto from &#8216;crypto&#8217;;<br \/>\nimport bcrypt from &#8216;bcrypt&#8217;;<br \/>\nimport jsonwebtoken from &#8216;jsonwebtoken&#8217;;<\/p>\n<p>class SecurityService {<br \/>\n  constructor() {<br \/>\n    this.algorithm = &#8216;aes-256-gcm&#8217;;<br \/>\n    this.keyLength = 32;<br \/>\n    this.ivLength = 16;<br \/>\n    this.tagLength = 16;<br \/>\n    this.saltRounds = 12;<br \/>\n  }<\/p>\n<p>  \/\/ Generate secure random key<br \/>\n  generateKey() {<br \/>\n    return crypto.randomBytes(this.keyLength);<br \/>\n  }<\/p>\n<p>  \/\/ Encrypt sensitive data<br \/>\n  encrypt(text, key) {<br \/>\n    const iv = crypto.randomBytes(this.ivLength);<br \/>\n    const cipher = crypto.createCipher(this.algorithm, key, iv);<\/p>\n<p>    let encrypted = cipher.update(text, &#8216;utf8&#8217;, &#8216;hex&#8217;);<br \/>\n    encrypted += cipher.final(&#8216;hex&#8217;);<\/p>\n<p>    const tag = cipher.getAuthTag();<\/p>\n<p>    return {<br \/>\n      iv: iv.toString(&#8216;hex&#8217;),<br \/>\n      encryptedData: encrypted,<br \/>\n      tag: tag.toString(&#8216;hex&#8217;)<br \/>\n    };<br \/>\n  }<\/p>\n<p>  \/\/ Decrypt sensitive data<br \/>\n  decrypt(encryptedData, key, iv, tag) {<br \/>\n    const decipher = crypto.createDecipher(this.algorithm, key, Buffer.from(iv, &#8216;hex&#8217;));<br \/>\n    decipher.setAuthTag(Buffer.from(tag, &#8216;hex&#8217;));<\/p>\n<p>    let decrypted = decipher.update(encryptedData, &#8216;hex&#8217;, &#8216;utf8&#8217;);<br \/>\n    decrypted += decipher.final(&#8216;utf8&#8242;);<\/p>\n<p>    return decrypted;<br \/>\n  }<\/p>\n<p>  \/\/ Secure password hashing<br \/>\n  async hashPassword(password) {<br \/>\n    const salt = await bcrypt.genSalt(this.saltRounds);<br \/>\n    return bcrypt.hash(password, salt);<br \/>\n  }<\/p>\n<p>  \/\/ Verify password<br \/>\n  async verifyPassword(password, hash) {<br \/>\n    return bcrypt.compare(password, hash);<br \/>\n  }<\/p>\n<p>  \/\/ Generate JWT token with security best practices<br \/>\n  generateToken(payload, expiresIn = &#8217;15m&#8217;) {<br \/>\n    const jwtPayload = {<br \/>\n      sub: payload.userId,<br \/>\n      email: payload.email,<br \/>\n      role: payload.role,<br \/>\n      iat: Math.floor(Date.now() \/ 1000),<br \/>\n      \/\/ Add custom claims<br \/>\n      permissions: payload.permissions || []<br \/>\n    };<\/p>\n<p>    const options = {<br \/>\n      expiresIn,<br \/>\n      issuer: process.env.JWT_ISSUER,<br \/>\n      audience: process.env.JWT_AUDIENCE,<br \/>\n      algorithm: &#8216;HS256&#8217;<br \/>\n    };<\/p>\n<p>    return jsonwebtoken.sign(jwtPayload, process.env.JWT_SECRET, options);<br \/>\n  }<\/p>\n<p>  \/\/ Verify JWT token<br \/>\n  verifyToken(token) {<br \/>\n    try {<br \/>\n      const decoded = jsonwebtoken.verify(token, process.env.JWT_SECRET, {<br \/>\n        issuer: process.env.JWT_ISSUER,<br \/>\n        audience: process.env.JWT_AUDIENCE,<br \/>\n        algorithms: [&#8216;HS256&#8217;]<br \/>\n      });<\/p>\n<p>      \/\/ Check token expiration buffer (5 minutes)<br \/>\n      const now = Math.floor(Date.now() \/ 1000);<br \/>\n      if (decoded.exp &#8211; now &lt; 300) {<br \/>\n        \/\/ Token will expire soon, consider refresh<br \/>\n        return { valid: true, decoded, needsRefresh: true };<br \/>\n      }<\/p>\n<p>      return { valid: true, decoded };<br \/>\n    } catch (error) {<br \/>\n      return { valid: false, error: error.message };<br \/>\n    }<br \/>\n  }<\/p>\n<p>  \/\/ Generate secure random session ID<br \/>\n  generateSessionId() {<br \/>\n    return crypto.randomBytes(32).toString(&#039;hex&#039;);<br \/>\n  }<\/p>\n<p>  \/\/ Generate API key<br \/>\n  generateApiKey() {<br \/>\n    const prefix = &#039;ak_&#039;;<br \/>\n    const key = crypto.randomBytes(32).toString(&#039;hex&#039;);<br \/>\n    return prefix + key;<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ Secure password policy implementation<br \/>\nclass PasswordPolicy {<br \/>\n  constructor() {<br \/>\n    this.minLength = 12;<br \/>\n    this.requireUppercase = true;<br \/>\n    this.requireLowercase = true;<br \/>\n    this.requireNumbers = true;<br \/>\n    this.requireSpecialChars = true;<br \/>\n    this.maxConsecutiveChars = 2;<br \/>\n    this.forbiddenPatterns = [&#039;password&#039;, &#039;123456&#039;, &#039;qwerty&#039;];<br \/>\n  }<\/p>\n<p>  validate(password) {<br \/>\n    const errors = [];<\/p>\n<p>    \/\/ Length check<br \/>\n    if (password.length &lt; this.minLength) {<br \/>\n      errors.push(`Password must be at least ${this.minLength} characters long`);<br \/>\n    }<\/p>\n<p>    \/\/ Character requirements<br \/>\n    if (this.requireUppercase &amp;&amp; !\/[A-Z]\/.test(password)) {<br \/>\n      errors.push(&#039;Password must contain at least one uppercase letter&#039;);<br \/>\n    }<\/p>\n<p>    if (this.requireLowercase &amp;&amp; !\/[a-z]\/.test(password)) {<br \/>\n      errors.push(&#039;Password must contain at least one lowercase letter&#039;);<br \/>\n    }<\/p>\n<p>    if (this.requireNumbers &amp;&amp; !\/\\d\/.test(password)) {<br \/>\n      errors.push(&#039;Password must contain at least one number&#039;);<br \/>\n    }<\/p>\n<p>    if (this.requireSpecialChars &amp;&amp; !\/[!@#$%^&amp;*(),.?&quot;:{}|]\/.test(password)) {<br \/>\n      errors.push(&#8216;Password must contain at least one special character&#8217;);<br \/>\n    }<\/p>\n<p>    \/\/ Consecutive characters check<br \/>\n    if (this.hasConsecutiveChars(password)) {<br \/>\n      errors.push(&#8216;Password cannot contain consecutive characters&#8217;);<br \/>\n    }<\/p>\n<p>    \/\/ Forbidden patterns check<br \/>\n    const lowerPassword = password.toLowerCase();<br \/>\n    for (const pattern of this.forbiddenPatterns) {<br \/>\n      if (lowerPassword.includes(pattern)) {<br \/>\n        errors.push(`Password cannot contain common patterns like &#8220;${pattern}&#8221;`);<br \/>\n      }<br \/>\n    }<\/p>\n<p>    return {<br \/>\n      isValid: errors.length === 0,<br \/>\n      errors<br \/>\n    };<br \/>\n  }<\/p>\n<p>  hasConsecutiveChars(password) {<br \/>\n    for (let i = 0; i &lt; password.length &#8211; this.maxConsecutiveChars; i++) {<br \/>\n      let isConsecutive = true;<br \/>\n      for (let j = 1; j &lt;= this.maxConsecutiveChars; j++) {<br \/>\n        if (password.charCodeAt(i + j) !== password.charCodeAt(i) + j) {<br \/>\n          isConsecutive = false;<br \/>\n          break;<br \/>\n        }<br \/>\n      }<br \/>\n      if (isConsecutive) return true;<br \/>\n    }<br \/>\n    return false;<br \/>\n  }<br \/>\n}<br \/>\n&#8220;`<\/p>\n<p>3. A03: Injection<br \/>\n&#8220;`javascript<br \/>\n\/\/ SQL Injection Prevention<\/p>\n<p>import { Pool } from &#039;pg&#039;;<br \/>\nimport mongoose from &#039;mongoose&#039;;<\/p>\n<p>\/\/ Secure Database Query Implementation<br \/>\nclass DatabaseService {<br \/>\n  constructor() {<br \/>\n    this.pool = new Pool({<br \/>\n      connectionString: process.env.DATABASE_URL,<br \/>\n      ssl: process.env.NODE_ENV === &#039;production&#039;<br \/>\n    });<br \/>\n  }<\/p>\n<p>  \/\/ VULNERABLE: SQL Injection prone<br \/>\n  async getUserUnsafe(email) {<br \/>\n    const query = `SELECT * FROM users WHERE email = &#039;${email}&#039;`;<br \/>\n    const result = await this.pool.query(query);<br \/>\n    return result.rows[0];<br \/>\n  }<\/p>\n<p>  \/\/ SECURE: Parameterized queries<br \/>\n  async getUser(email) {<br \/>\n    const query = &#039;SELECT * FROM users WHERE email = $1&#039;;<br \/>\n    const result = await this.pool.query(query, [email]);<br \/>\n    return result.rows[0];<br \/>\n  }<\/p>\n<p>  \/\/ Secure ORM usage dengan Mongoose<br \/>\n  async findUserByEmail(email) {<br \/>\n    try {<br \/>\n      const user = await User.findOne({ email: email.trim().toLowerCase() });<br \/>\n      return user;<br \/>\n    } catch (error) {<br \/>\n      throw new Error(&#039;Database query failed&#039;);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  \/\/ Advanced query dengan input validation<br \/>\n  async searchUsers(filters, pagination) {<br \/>\n    \/\/ Validate inputs<br \/>\n    const validatedFilters = this.validateFilters(filters);<br \/>\n    const validatedPagination = this.validatePagination(pagination);<\/p>\n<p>    \/\/ Build query dynamically but safely<br \/>\n    const query = {};<br \/>\n    const options = {<br \/>\n      limit: validatedPagination.limit,<br \/>\n      skip: validatedPagination.offset,<br \/>\n      sort: validatedPagination.sort<br \/>\n    };<\/p>\n<p>    if (validatedFilters.name) {<br \/>\n      query.name = { $regex: validatedFilters.name, $options: &#039;i&#039; };<br \/>\n    }<\/p>\n<p>    if (validatedFilters.role) {<br \/>\n      query.role = validatedFilters.role;<br \/>\n    }<\/p>\n<p>    if (validatedFilters.minAge) {<br \/>\n      query.age = { $gte: parseInt(validatedFilters.minAge) };<br \/>\n    }<\/p>\n<p>    const users = await User.find(query, null, options);<br \/>\n    return users;<br \/>\n  }<\/p>\n<p>  validateFilters(filters) {<br \/>\n    const validated = {};<br \/>\n    const allowedFields = [&#039;name&#039;, &#039;role&#039;, &#039;minAge&#039;, &#039;maxAge&#039;];<\/p>\n<p>    for (const [key, value] of Object.entries(filters)) {<br \/>\n      if (allowedFields.includes(key)) {<br \/>\n        \/\/ Sanitize input<br \/>\n        if (typeof value === &#039;string&#039;) {<br \/>\n          validated[key] = value.trim().replace(\/[]\/g, &#8221;);<br \/>\n        } else if (typeof value === &#8216;number&#8217;) {<br \/>\n          validated[key] = Math.max(0, value); \/\/ Ensure positive numbers<br \/>\n        }<br \/>\n      }<br \/>\n    }<\/p>\n<p>    return validated;<br \/>\n  }<\/p>\n<p>  validatePagination(pagination) {<br \/>\n    const limit = Math.min(Math.max(1, parseInt(pagination.limit) || 10), 100);<br \/>\n    const page = Math.max(1, parseInt(pagination.page) || 1);<br \/>\n    const offset = (page &#8211; 1) * limit;<\/p>\n<p>    const allowedSortFields = [&#8216;name&#8217;, &#8216;createdAt&#8217;, &#8216;updatedAt&#8217;];<br \/>\n    const sortField = allowedSortFields.includes(pagination.sortBy)<br \/>\n      ? pagination.sortBy<br \/>\n      : &#8216;createdAt&#8217;;<\/p>\n<p>    const sortOrder = pagination.sortOrder === &#8216;desc&#8217; ? -1 : 1;<\/p>\n<p>    return {<br \/>\n      limit,<br \/>\n      offset,<br \/>\n      sort: { [sortField]: sortOrder }<br \/>\n    };<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ NoSQL Injection Prevention<br \/>\nclass NoSQLSecurity {<br \/>\n  \/\/ VULNERABLE: Direct query injection<br \/>\n  async findUsersUnsafe(filters) {<br \/>\n    return User.find(JSON.parse(filters));<br \/>\n  }<\/p>\n<p>  \/\/ SECURE: Proper query building<br \/>\n  async findUsers(filters) {<br \/>\n    const query = {};<\/p>\n<p>    \/\/ Validate each filter key<br \/>\n    if (filters.email &amp;&amp; this.isValidEmail(filters.email)) {<br \/>\n      query.email = filters.email.toLowerCase().trim();<br \/>\n    }<\/p>\n<p>    if (filters.role &amp;&amp; this.isValidRole(filters.role)) {<br \/>\n      query.role = filters.role;<br \/>\n    }<\/p>\n<p>    if (filters.age) {<br \/>\n      const age = parseInt(filters.age);<br \/>\n      if (!isNaN(age) &amp;&amp; age &gt;= 0 &amp;&amp; age  {<br \/>\n      const apiKey = req.headers[&#8216;x-api-key&#8217;];<\/p>\n<p>      if (!apiKey) {<br \/>\n        return res.status(401).json({<br \/>\n          error: &#8216;API key required&#8217;,<br \/>\n          code: &#8216;MISSING_API_KEY&#8217;<br \/>\n        });<br \/>\n      }<\/p>\n<p>      try {<br \/>\n        const keyRecord = await APIKey.findOne({<br \/>\n          key: apiKey,<br \/>\n          isActive: true<br \/>\n        }).populate(&#8216;user&#8217;);<\/p>\n<p>        if (!keyRecord) {<br \/>\n          return res.status(401).json({<br \/>\n            error: &#8216;Invalid API key&#8217;,<br \/>\n            code: &#8216;INVALID_API_KEY&#8217;<br \/>\n          });<br \/>\n        }<\/p>\n<p>        \/\/ Check rate limit for this API key<br \/>\n        const rateLimitResult = this.checkAPIKeyRateLimit(keyRecord);<br \/>\n        if (!rateLimitResult.allowed) {<br \/>\n          return res.status(429).json({<br \/>\n            error: &#8216;Rate limit exceeded&#8217;,<br \/>\n            code: &#8216;RATE_LIMIT_EXCEEDED&#8217;,<br \/>\n            resetTime: rateLimitResult.resetTime<br \/>\n          });<br \/>\n        }<\/p>\n<p>        \/\/ Attach key info to request<br \/>\n        req.apiKey = keyRecord;<br \/>\n        req.user = keyRecord.user;<\/p>\n<p>        next();<br \/>\n      } catch (error) {<br \/>\n        res.status(500).json({<br \/>\n          error: &#8216;Authentication failed&#8217;,<br \/>\n          code: &#8216;AUTH_ERROR&#8217;<br \/>\n        });<br \/>\n      }<br \/>\n    };<br \/>\n  }<\/p>\n<p>  \/\/ JWT authentication<br \/>\n  authenticateJWT() {<br \/>\n    return (req, res, next) =&gt; {<br \/>\n      const authHeader = req.headers.authorization;<br \/>\n      const token = authHeader &amp;&amp; authHeader.split(&#8216; &#8216;)[1];<\/p>\n<p>      if (!token) {<br \/>\n        return res.status(401).json({<br \/>\n          error: &#8216;Access token required&#8217;,<br \/>\n          code: &#8216;MISSING_TOKEN&#8217;<br \/>\n        });<br \/>\n      }<\/p>\n<p>      const security = new SecurityService();<br \/>\n      const result = security.verifyToken(token);<\/p>\n<p>      if (!result.valid) {<br \/>\n        return res.status(401).json({<br \/>\n          error: &#8216;Invalid token&#8217;,<br \/>\n          code: &#8216;INVALID_TOKEN&#8217;,<br \/>\n          details: result.error<br \/>\n        });<br \/>\n      }<\/p>\n<p>      req.user = result.decoded;<br \/>\n      req.needsTokenRefresh = result.needsRefresh;<\/p>\n<p>      next();<br \/>\n    };<br \/>\n  }<\/p>\n<p>  \/\/ IP-based rate limiting<br \/>\n  rateLimit(options = {}) {<br \/>\n    const {<br \/>\n      windowMs = 15 * 60 * 1000, \/\/ 15 minutes<br \/>\n      max = 100, \/\/ requests per window<br \/>\n      message = &#8216;Too many requests&#8217;<br \/>\n    } = options;<\/p>\n<p>    return (req, res, next) =&gt; {<br \/>\n      const clientId = req.ip || req.connection.remoteAddress;<br \/>\n      const now = Date.now();<\/p>\n<p>      \/\/ Check if IP is blocked<br \/>\n      if (this.blockedIPs.has(clientId)) {<br \/>\n        return res.status(429).json({<br \/>\n          error: &#8216;IP address blocked&#8217;,<br \/>\n          code: &#8216;IP_BLOCKED&#8217;<br \/>\n        });<br \/>\n      }<\/p>\n<p>      \/\/ Initialize or get client data<br \/>\n      if (!this.rateLimiter.has(clientId)) {<br \/>\n        this.rateLimiter.set(clientId, {<br \/>\n          count: 0,<br \/>\n          resetTime: now + windowMs,<br \/>\n          firstRequest: now<br \/>\n        });<br \/>\n      }<\/p>\n<p>      const clientData = this.rateLimiter.get(clientId);<\/p>\n<p>      \/\/ Reset window if expired<br \/>\n      if (now &gt; clientData.resetTime) {<br \/>\n        clientData.count = 0;<br \/>\n        clientData.resetTime = now + windowMs;<br \/>\n      }<\/p>\n<p>      \/\/ Increment count<br \/>\n      clientData.count++;<\/p>\n<p>      \/\/ Check if limit exceeded<br \/>\n      if (clientData.count &gt; max) {<br \/>\n        \/\/ Block IP if severely exceeded<br \/>\n        if (clientData.count &gt; max * 5) {<br \/>\n          this.blockedIPs.add(clientId);<br \/>\n          setTimeout(() =&gt; {<br \/>\n            this.blockedIPs.delete(clientId);<br \/>\n          }, windowMs * 2);<br \/>\n        }<\/p>\n<p>        return res.status(429).json({<br \/>\n          error: message,<br \/>\n          code: &#8216;RATE_LIMIT_EXCEEDED&#8217;,<br \/>\n          limit: max,<br \/>\n          remaining: 0,<br \/>\n          resetTime: clientData.resetTime<br \/>\n        });<br \/>\n      }<\/p>\n<p>      \/\/ Add rate limit headers<br \/>\n      res.setHeader(&#8216;X-RateLimit-Limit&#8217;, max);<br \/>\n      res.setHeader(&#8216;X-RateLimit-Remaining&#8217;, Math.max(0, max &#8211; clientData.count));<br \/>\n      res.setHeader(&#8216;X-RateLimit-Reset&#8217;, clientData.resetTime);<\/p>\n<p>      next();<br \/>\n    };<br \/>\n  }<\/p>\n<p>  \/\/ CORS configuration<br \/>\n  configureCORS() {<br \/>\n    return (req, res, next) =&gt; {<br \/>\n      const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(&#8216;,&#8217;) || [&#8216;http:\/\/localhost:3000&#8217;];<br \/>\n      const origin = req.headers.origin;<\/p>\n<p>      if (allowedOrigins.includes(origin)) {<br \/>\n        res.setHeader(&#8216;Access-Control-Allow-Origin&#8217;, origin);<br \/>\n      }<\/p>\n<p>      res.setHeader(&#8216;Access-Control-Allow-Methods&#8217;, &#8216;GET, POST, PUT, DELETE, OPTIONS&#8217;);<br \/>\n      res.setHeader(&#8216;Access-Control-Allow-Headers&#8217;, &#8216;Content-Type, Authorization, X-API-Key&#8217;);<br \/>\n      res.setHeader(&#8216;Access-Control-Allow-Credentials&#8217;, &#8216;true&#8217;);<br \/>\n      res.setHeader(&#8216;Access-Control-Max-Age&#8217;, &#8216;86400&#8217;); \/\/ 24 hours<\/p>\n<p>      if (req.method === &#8216;OPTIONS&#8217;) {<br \/>\n        return res.status(204).end();<br \/>\n      }<\/p>\n<p>      next();<br \/>\n    };<br \/>\n  }<\/p>\n<p>  \/\/ Request validation middleware<br \/>\n  validateRequest(schema) {<br \/>\n    return (req, res, next) =&gt; {<br \/>\n      const { error } = schema.validate(req.body);<\/p>\n<p>      if (error) {<br \/>\n        return res.status(400).json({<br \/>\n          error: &#8216;Validation failed&#8217;,<br \/>\n          details: error.details.map(detail =&gt; ({<br \/>\n            field: detail.path.join(&#8216;.&#8217;),<br \/>\n            message: detail.message<br \/>\n          }))<br \/>\n        });<br \/>\n      }<\/p>\n<p>      next();<br \/>\n    };<br \/>\n  }<\/p>\n<p>  \/\/ API response security headers<br \/>\n  setSecurityHeaders() {<br \/>\n    return (req, res, next) =&gt; {<br \/>\n      res.setHeader(&#8216;X-Content-Type-Options&#8217;, &#8216;nosniff&#8217;);<br \/>\n      res.setHeader(&#8216;X-Frame-Options&#8217;, &#8216;DENY&#8217;);<br \/>\n      res.setHeader(&#8216;X-XSS-Protection&#8217;, &#8216;1; mode=block&#8217;);<br \/>\n      res.setHeader(&#8216;Referrer-Policy&#8217;, &#8216;strict-origin-when-cross-origin&#8217;);<br \/>\n      res.setHeader(&#8216;Permissions-Policy&#8217;, &#8216;camera=(), microphone=(), geolocation=()&#8217;);<\/p>\n<p>      next();<br \/>\n    };<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ Usage<br \/>\nconst apiSecurity = new APISecurity();<\/p>\n<p>app.use(apiSecurity.configureCORS());<br \/>\napp.use(apiSecurity.setSecurityHeaders());<\/p>\n<p>\/\/ Public endpoints with rate limiting<br \/>\napp.get(&#8216;\/api\/public&#8217;, apiSecurity.rateLimit({ max: 10 }), (req, res) =&gt; {<br \/>\n  res.json({ message: &#8216;Public endpoint&#8217; });<br \/>\n});<\/p>\n<p>\/\/ Protected endpoints with authentication<br \/>\napp.get(&#8216;\/api\/users&#8217;,<br \/>\n  apiSecurity.authenticateJWT(),<br \/>\n  apiSecurity.rateLimit({ max: 100 }),<br \/>\n  (req, res) =&gt; {<br \/>\n    res.json({ users: [] });<br \/>\n  }<br \/>\n);<\/p>\n<p>\/\/ API endpoints with API key authentication<br \/>\napp.post(&#8216;\/api\/webhook&#8217;,<br \/>\n  apiSecurity.authenticateAPIKey(),<br \/>\n  (req, res) =&gt; {<br \/>\n    res.json({ received: true });<br \/>\n  }<br \/>\n);<br \/>\n&#8220;`<\/p>\n<p>Security Monitoring dan Logging<\/p>\n<p>1. Security Event Monitoring<br \/>\n&#8220;`javascript<br \/>\n\/\/ Security Monitoring System<\/p>\n<p>class SecurityMonitor {<br \/>\n  constructor() {<br \/>\n    this.events = [];<br \/>\n    this.alertThresholds = {<br \/>\n      failedLogins: 5,<br \/>\n      suspiciousActivity: 10,<br \/>\n      dataAccess: 50<br \/>\n    };<br \/>\n    this.blockedIPs = new Set();<br \/>\n  }<\/p>\n<p>  \/\/ Log security events<br \/>\n  logSecurityEvent(event) {<br \/>\n    const securityEvent = {<br \/>\n      timestamp: new Date().toISOString(),<br \/>\n      type: event.type,<br \/>\n      severity: event.severity || &#8216;medium&#8217;,<br \/>\n      ip: event.ip,<br \/>\n      userId: event.userId,<br \/>\n      userAgent: event.userAgent,<br \/>\n      details: event.details,<br \/>\n      metadata: event.metadata || {}<br \/>\n    };<\/p>\n<p>    this.events.push(securityEvent);<\/p>\n<p>    \/\/ Check for alerts<br \/>\n    this.checkAlerts(securityEvent);<\/p>\n<p>    \/\/ Store in persistent storage<br \/>\n    this.persistEvent(securityEvent);<\/p>\n<p>    console.log(`Security Event [${event.severity}]: ${event.type}`, securityEvent);<br \/>\n  }<\/p>\n<p>  \/\/ Failed login attempt<br \/>\n  logFailedLogin(ip, email, userAgent) {<br \/>\n    this.logSecurityEvent({<br \/>\n      type: &#8216;FAILED_LOGIN&#8217;,<br \/>\n      severity: &#8216;high&#8217;,<br \/>\n      ip,<br \/>\n      userId: email,<br \/>\n      userAgent,<br \/>\n      details: &#8216;Failed login attempt&#8217;<br \/>\n    });<br \/>\n  }<\/p>\n<p>  \/\/ Successful login<br \/>\n  logSuccessfulLogin(userId, ip, userAgent) {<br \/>\n    this.logSecurityEvent({<br \/>\n      type: &#8216;SUCCESSFUL_LOGIN&#8217;,<br \/>\n      severity: &#8216;info&#8217;,<br \/>\n      ip,<br \/>\n      userId,<br \/>\n      userAgent,<br \/>\n      details: &#8216;User logged in successfully&#8217;<br \/>\n    });<br \/>\n  }<\/p>\n<p>  \/\/ Suspicious activity detection<br \/>\n  logSuspiciousActivity(ip, userId, activity) {<br \/>\n    this.logSecurityEvent({<br \/>\n      type: &#8216;SUSPICIOUS_ACTIVITY&#8217;,<br \/>\n      severity: &#8216;high&#8217;,<br \/>\n      ip,<br \/>\n      userId,<br \/>\n      details: activity<br \/>\n    });<br \/>\n  }<\/p>\n<p>  \/\/ Data access logging<br \/>\n  logDataAccess(userId, resource, action, ip) {<br \/>\n    this.logSecurityEvent({<br \/>\n      type: &#8216;DATA_ACCESS&#8217;,<br \/>\n      severity: &#8216;medium&#8217;,<br \/>\n      ip,<br \/>\n      userId,<br \/>\n      details: `${action} on ${resource}`,<br \/>\n      metadata: { resource, action }<br \/>\n    });<br \/>\n  }<\/p>\n<p>  \/\/ Check for security alerts<br \/>\n  checkAlerts(event) {<br \/>\n    const recentEvents = this.getRecentEvents(15 * 60 * 1000); \/\/ Last 15 minutes<\/p>\n<p>    \/\/ Check for multiple failed logins<br \/>\n    const failedLogins = recentEvents.filter(e =&gt;<br \/>\n      e.type === &#8216;FAILED_LOGIN&#8217; &amp;&amp; e.ip === event.ip<br \/>\n    );<\/p>\n<p>    if (failedLogins.length &gt;= this.alertThresholds.failedLogins) {<br \/>\n      this.triggerAlert({<br \/>\n        type: &#8216;BRUTE_FORCE_DETECTED&#8217;,<br \/>\n        severity: &#8216;critical&#8217;,<br \/>\n        ip: event.ip,<br \/>\n        details: `${failedLogins.length} failed login attempts detected`<br \/>\n      });<\/p>\n<p>      \/\/ Block IP temporarily<br \/>\n      this.blockIP(event.ip, 60 * 60 * 1000); \/\/ 1 hour<br \/>\n    }<\/p>\n<p>    \/\/ Check for suspicious activity patterns<br \/>\n    const suspiciousActivity = recentEvents.filter(e =&gt;<br \/>\n      e.type === &#8216;SUSPICIOUS_ACTIVITY&#8217; &amp;&amp; e.ip === event.ip<br \/>\n    );<\/p>\n<p>    if (suspiciousActivity.length &gt;= this.alertThresholds.suspiciousActivity) {<br \/>\n      this.triggerAlert({<br \/>\n        type: &#8216;SUSPICIOUS_PATTERN_DETECTED&#8217;,<br \/>\n        severity: &#8216;critical&#8217;,<br \/>\n        ip: event.ip,<br \/>\n        details: `${suspiciousActivity.length} suspicious activities detected`<br \/>\n      });<br \/>\n    }<\/p>\n<p>    \/\/ Check for unusual data access<br \/>\n    const dataAccess = recentEvents.filter(e =&gt;<br \/>\n      e.type === &#8216;DATA_ACCESS&#8217; &amp;&amp; e.userId === event.userId<br \/>\n    );<\/p>\n<p>    if (dataAccess.length &gt;= this.alertThresholds.dataAccess) {<br \/>\n      this.triggerAlert({<br \/>\n        type: &#8216;UNUSUAL_DATA_ACCESS&#8217;,<br \/>\n        severity: &#8216;high&#8217;,<br \/>\n        userId: event.userId,<br \/>\n        details: `${dataAccess.length} data access operations detected`<br \/>\n      });<br \/>\n    }<br \/>\n  }<\/p>\n<p>  \/\/ Trigger security alert<br \/>\n  async triggerAlert(alert) {<br \/>\n    console.error(&#8216;SECURITY ALERT:&#8217;, alert);<\/p>\n<p>    \/\/ Send notification<br \/>\n    await this.sendNotification(alert);<\/p>\n<p>    \/\/ Store alert<br \/>\n    await this.persistAlert(alert);<br \/>\n  }<\/p>\n<p>  \/\/ Send notification (email, Slack, etc.)<br \/>\n  async sendNotification(alert) {<br \/>\n    \/\/ Implementation depends on notification service<br \/>\n    const notification = {<br \/>\n      title: `Security Alert: ${alert.type}`,<br \/>\n      message: alert.details,<br \/>\n      severity: alert.severity,<br \/>\n      timestamp: new Date().toISOString()<br \/>\n    };<\/p>\n<p>    \/\/ Send to security team<br \/>\n    if (alert.severity === &#8216;critical&#8217;) {<br \/>\n      \/\/ Immediate notification for critical alerts<br \/>\n      await this.sendCriticalNotification(notification);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  \/\/ Block IP temporarily<br \/>\n  blockIP(ip, duration) {<br \/>\n    this.blockedIPs.add(ip);<\/p>\n<p>    setTimeout(() =&gt; {<br \/>\n      this.blockedIPs.delete(ip);<br \/>\n    }, duration);<\/p>\n<p>    this.logSecurityEvent({<br \/>\n      type: &#8216;IP_BLOCKED&#8217;,<br \/>\n      severity: &#8216;high&#8217;,<br \/>\n      ip,<br \/>\n      details: `IP blocked for ${duration}ms`<br \/>\n    });<br \/>\n  }<\/p>\n<p>  \/\/ Get recent events<br \/>\n  getRecentEvents(timeWindow) {<br \/>\n    const now = Date.now();<br \/>\n    return this.events.filter(event =&gt;<br \/>\n      new Date(event.timestamp).getTime() &gt; (now &#8211; timeWindow)<br \/>\n    );<br \/>\n  }<\/p>\n<p>  \/\/ Persist event to database<br \/>\n  async persistEvent(event) {<br \/>\n    try {<br \/>\n      \/\/ Store in database for long-term analysis<br \/>\n      await SecurityEvent.create(event);<br \/>\n    } catch (error) {<br \/>\n      console.error(&#8216;Failed to persist security event:&#8217;, error);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  \/\/ Persist alert to database<br \/>\n  async persistAlert(alert) {<br \/>\n    try {<br \/>\n      await SecurityAlert.create({<br \/>\n        &#8230;alert,<br \/>\n        timestamp: new Date(),<br \/>\n        status: &#8216;active&#8217;<br \/>\n      });<br \/>\n    } catch (error) {<br \/>\n      console.error(&#8216;Failed to persist security alert:&#8217;, error);<br \/>\n    }<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ Security monitoring middleware<br \/>\nclass SecurityMiddleware {<br \/>\n  constructor(securityMonitor) {<br \/>\n    this.monitor = securityMonitor;<br \/>\n  }<\/p>\n<p>  \/\/ Monitor login attempts<br \/>\n  monitorLogin() {<br \/>\n    return (req, res, next) =&gt; {<br \/>\n      const originalSend = res.send;<\/p>\n<p>      res.send = function(data) {<br \/>\n        \/\/ Log failed login attempts<br \/>\n        if (res.statusCode === 401 || res.statusCode === 403) {<br \/>\n          this.monitor.logFailedLogin(<br \/>\n            req.ip,<br \/>\n            req.body.email,<br \/>\n            req.get(&#8216;User-Agent&#8217;)<br \/>\n          );<br \/>\n        }<\/p>\n<p>        \/\/ Log successful login<br \/>\n        if (res.statusCode === 200 &amp;&amp; req.path === &#8216;\/api\/login&#8217;) {<br \/>\n          this.monitor.logSuccessfulLogin(<br \/>\n            req.user?.id,<br \/>\n            req.ip,<br \/>\n            req.get(&#8216;User-Agent&#8217;)<br \/>\n          );<br \/>\n        }<\/p>\n<p>        originalSend.call(this, data);<br \/>\n      }.bind(this);<\/p>\n<p>      next();<br \/>\n    };<br \/>\n  }<\/p>\n<p>  \/\/ Monitor API access<br \/>\n  monitorAPIAccess() {<br \/>\n    return (req, res, next) =&gt; {<br \/>\n      const originalSend = res.send;<\/p>\n<p>      res.send = function(data) {<br \/>\n        \/\/ Log data access<br \/>\n        if (req.user &amp;&amp; req.method !== &#8216;GET&#8217;) {<br \/>\n          this.monitor.logDataAccess(<br \/>\n            req.user.id,<br \/>\n            req.path,<br \/>\n            req.method,<br \/>\n            req.ip<br \/>\n          );<br \/>\n        }<\/p>\n<p>        originalSend.call(this, data);<br \/>\n      }.bind(this);<\/p>\n<p>      next();<br \/>\n    };<br \/>\n  }<\/p>\n<p>  \/\/ Detect suspicious patterns<br \/>\n  detectSuspiciousActivity() {<br \/>\n    return (req, res, next) =&gt; {<br \/>\n      const suspiciousPatterns = [<br \/>\n        \/\\.\\.\/,  \/\/ Directory traversal<br \/>\n        \/ r.passed).length;<br \/>\n    const total = results.length;<br \/>\n    const critical = results.filter(r =&gt;<br \/>\n      !r.passed &amp;&amp; r.details?.severity === &#8216;critical&#8217;<br \/>\n    ).length;<\/p>\n<p>    return {<br \/>\n      summary: {<br \/>\n        passed,<br \/>\n        failed: total &#8211; passed,<br \/>\n        total,<br \/>\n        passRate: (passed \/ total) * 100,<br \/>\n        criticalIssues: critical<br \/>\n      },<br \/>\n      results<br \/>\n    };<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ Common security tests<br \/>\nconst securityTests = new SecurityTestSuite();<\/p>\n<p>\/\/ Test for SQL injection vulnerabilities<br \/>\nsecurityTests.addTest(&#8216;SQL Injection Test&#8217;, async () =&gt; {<br \/>\n  const maliciousInputs = [<br \/>\n    &#8220;&#8216; OR &#8216;1&#8217;=&#8217;1&#8221;,<br \/>\n    &#8220;&#8216;; DROP TABLE users; &#8211;&#8220;,<br \/>\n    &#8220;&#8216; UNION SELECT * FROM users &#8211;&#8221;<br \/>\n  ];<\/p>\n<p>  for (const input of maliciousInputs) {<br \/>\n    try {<br \/>\n      const response = await fetch(&#8216;\/api\/users&#8217;, {<br \/>\n        method: &#8216;POST&#8217;,<br \/>\n        headers: { &#8216;Content-Type&#8217;: &#8216;application\/json&#8217; },<br \/>\n        body: JSON.stringify({ email: input, password: &#8216;test&#8217; })<br \/>\n      });<\/p>\n<p>      if (response.status === 200) {<br \/>\n        return {<br \/>\n          passed: false,<br \/>\n          message: &#8216;Potential SQL injection vulnerability detected&#8217;,<br \/>\n          details: { severity: &#8216;critical&#8217;, input }<br \/>\n        };<br \/>\n      }<br \/>\n    } catch (error) {<br \/>\n      \/\/ Network errors are expected for malformed requests<br \/>\n    }<br \/>\n  }<\/p>\n<p>  return { passed: true, message: &#8216;No SQL injection vulnerabilities detected&#8217; };<br \/>\n}, &#8216;injection&#8217;);<\/p>\n<p>\/\/ Test for XSS vulnerabilities<br \/>\nsecurityTests.addTest(&#8216;XSS Protection Test&#8217;, async () =&gt; {<br \/>\n  const xssPayloads = [<br \/>\n    &#8216;alert(&#8220;XSS&#8221;)&#8217;,<br \/>\n    &#8216;<img decoding=\"async\" src=\"x\">&#8216;,<br \/>\n    &#8216;javascript:alert(&#8220;XSS&#8221;)&#8217;<br \/>\n  ];<\/p>\n<p>  for (const payload of xssPayloads) {<br \/>\n    try {<br \/>\n      const response = await fetch(&#8216;\/api\/comments&#8217;, {<br \/>\n        method: &#8216;POST&#8217;,<br \/>\n        headers: { &#8216;Content-Type&#8217;: &#8216;application\/json&#8217; },<br \/>\n        body: JSON.stringify({ content: payload })<br \/>\n      });<\/p>\n<p>      if (response.status === 200) {<br \/>\n        const data = await response.json();<\/p>\n<p>        \/\/ Check if XSS payload was not sanitized<br \/>\n        if (data.content &amp;&amp; data.content.includes(&#8221;)) {<br \/>\n          return {<br \/>\n            passed: false,<br \/>\n            message: &#8216;XSS vulnerability detected&#8217;,<br \/>\n            details: { severity: &#8216;high&#8217;, payload }<br \/>\n          };<br \/>\n        }<br \/>\n      }<br \/>\n    } catch (error) {<br \/>\n      \/\/ Network errors are expected<br \/>\n    }<br \/>\n  }<\/p>\n<p>  return { passed: true, message: &#8216;No XSS vulnerabilities detected&#8217; };<br \/>\n}, &#8216;xss&#8217;);<\/p>\n<p>\/\/ Test for authentication bypass<br \/>\nsecurityTests.addTest(&#8216;Authentication Bypass Test&#8217;, async () =&gt; {<br \/>\n  try {<br \/>\n    \/\/ Try to access protected endpoint without token<br \/>\n    const response = await fetch(&#8216;\/api\/users\/profile&#8217;, {<br \/>\n      headers: { &#8216;Content-Type&#8217;: &#8216;application\/json&#8217; }<br \/>\n    });<\/p>\n<p>    if (response.status === 200) {<br \/>\n      return {<br \/>\n        passed: false,<br \/>\n        message: &#8216;Authentication bypass vulnerability detected&#8217;,<br \/>\n        details: { severity: &#8216;critical&#8217; }<br \/>\n      };<br \/>\n    }<\/p>\n<p>    \/\/ Try with invalid token<br \/>\n    const invalidResponse = await fetch(&#8216;\/api\/users\/profile&#8217;, {<br \/>\n      headers: {<br \/>\n        &#8216;Content-Type&#8217;: &#8216;application\/json&#8217;,<br \/>\n        &#8216;Authorization&#8217;: &#8216;Bearer invalid.token.here&#8217;<br \/>\n      }<br \/>\n    });<\/p>\n<p>    if (invalidResponse.status === 200) {<br \/>\n      return {<br \/>\n        passed: false,<br \/>\n        message: &#8216;Invalid token accepted&#8217;,<br \/>\n        details: { severity: &#8216;critical&#8217; }<br \/>\n      };<br \/>\n    }<\/p>\n<p>    return { passed: true, message: &#8216;Authentication is properly secured&#8217; };<br \/>\n  } catch (error) {<br \/>\n    return {<br \/>\n      passed: false,<br \/>\n      message: &#8216;Authentication test failed&#8217;,<br \/>\n      details: { error: error.message }<br \/>\n    };<br \/>\n  }<br \/>\n}, &#8216;auth&#8217;);<\/p>\n<p>\/\/ Test for rate limiting<br \/>\nsecurityTests.addTest(&#8216;Rate Limiting Test&#8217;, async () =&gt; {<br \/>\n  const requests = [];<\/p>\n<p>  \/\/ Send multiple requests quickly<br \/>\n  for (let i = 0; i  r.status === 200).length;<\/p>\n<p>  if (successCount &gt;= 15) {<br \/>\n    return {<br \/>\n      passed: false,<br \/>\n      message: &#8216;Rate limiting not working or too permissive&#8217;,<br \/>\n      details: { severity: &#8216;medium&#8217;, successCount }<br \/>\n    };<br \/>\n  }<\/p>\n<p>  return { passed: true, message: &#8216;Rate limiting is working properly&#8217; };<br \/>\n}, &#8216;infrastructure&#8217;);<\/p>\n<p>\/\/ Test for HTTPS enforcement<br \/>\nsecurityTests.addTest(&#8216;HTTPS Enforcement Test&#8217;, async () =&gt; {<br \/>\n  if (process.env.NODE_ENV === &#8216;production&#8217;) {<br \/>\n    try {<br \/>\n      \/\/ Try HTTP request (should fail or redirect)<br \/>\n      const response = await fetch(`http:\/\/${process.env.DOMAIN}\/api\/health`);<\/p>\n<p>      if (response.status === 200 || response.url.startsWith(&#8216;https:\/\/&#8217;)) {<br \/>\n        return { passed: true, message: &#8216;HTTPS properly enforced&#8217; };<br \/>\n      } else {<br \/>\n        return {<br \/>\n          passed: false,<br \/>\n          message: &#8216;HTTPS not properly enforced&#8217;,<br \/>\n          details: { severity: &#8216;high&#8217; }<br \/>\n        };<br \/>\n      }<br \/>\n    } catch (error) {<br \/>\n      \/\/ Expected if HTTP is not available<br \/>\n      return { passed: true, message: &#8216;HTTPS properly configured&#8217; };<br \/>\n    }<br \/>\n  } else {<br \/>\n    return { passed: true, message: &#8216;HTTPS test skipped in development&#8217; };<br \/>\n  }<br \/>\n}, &#8216;infrastructure&#8217;);<\/p>\n<p>\/\/ Run security tests in CI\/CD<br \/>\nasync function runSecurityTests() {<br \/>\n  console.log(&#8216;Running security tests&#8230;&#8217;);<\/p>\n<p>  const results = await securityTests.runTests();<br \/>\n  const report = securityTests.generateReport(results);<\/p>\n<p>  console.log(&#8216;Security Test Results:&#8217;, report.summary);<\/p>\n<p>  \/\/ Fail build if critical issues found<br \/>\n  if (report.summary.criticalIssues &gt; 0) {<br \/>\n    console.error(&#8216;Critical security issues detected!&#8217;);<br \/>\n    process.exit(1);<br \/>\n  }<\/p>\n<p>  return report;<br \/>\n}<\/p>\n<p>\/\/ Export untuk use in CI\/CD<br \/>\nmodule.exports = { runSecurityTests, securityTests };<br \/>\n&#8220;`<\/p>\n<p>Compliance dan Regulatory Requirements<\/p>\n<p>1. Data Protection Compliance<br \/>\n&#8220;`javascript<br \/>\n\/\/ Personal Data Protection Act (PDPA) Compliance<\/p>\n<p>class PDPACompliance {<br \/>\n  constructor() {<br \/>\n    this.sensitiveDataTypes = [<br \/>\n      &#8216;nik&#8217;, \/\/ National ID<br \/>\n      &#8216;passport&#8217;,<br \/>\n      &#8216;bankAccount&#8217;,<br \/>\n      &#8216;creditCard&#8217;,<br \/>\n      &#8216;medical&#8217;,<br \/>\n      &#8216;biometric&#8217;<br \/>\n    ];<br \/>\n  }<\/p>\n<p>  \/\/ Consent management<br \/>\n  async recordConsent(userId, consentType, consentData) {<br \/>\n    const consent = {<br \/>\n      userId,<br \/>\n      type: consentType,<br \/>\n      granted: consentData.granted,<br \/>\n      purpose: consentData.purpose,<br \/>\n      timestamp: new Date(),<br \/>\n      ipAddress: consentData.ipAddress,<br \/>\n      userAgent: consentData.userAgent,<br \/>\n      documentVersion: consentData.documentVersion<br \/>\n    };<\/p>\n<p>    await Consent.create(consent);<br \/>\n    return consent;<br \/>\n  }<\/p>\n<p>  \/\/ Data anonymization<br \/>\n  anonymizePersonalData(data, fieldsToAnonymize) {<br \/>\n    const anonymized = { &#8230;data };<\/p>\n<p>    fieldsToAnonymize.forEach(field =&gt; {<br \/>\n      if (anonymized[field]) {<br \/>\n        anonymized[field] = this.anonymizeValue(anonymized[field]);<br \/>\n      }<br \/>\n    });<\/p>\n<p>    return anonymized;<br \/>\n  }<\/p>\n<p>  anonymizeValue(value) {<br \/>\n    if (typeof value !== &#8216;string&#8217;) return value;<\/p>\n<p>    \/\/ Keep first 2 dan last 2 characters<br \/>\n    if (value.length &lt;= 4) {<br \/>\n      return &#039;*&#039;.repeat(value.length);<br \/>\n    }<\/p>\n<p>    const start = value.substring(0, 2);<br \/>\n    const end = value.substring(value.length &#8211; 2);<br \/>\n    const middle = &#039;*&#039;.repeat(value.length &#8211; 4);<\/p>\n<p>    return start + middle + end;<br \/>\n  }<\/p>\n<p>  \/\/ Data retention management<br \/>\n  async applyRetentionPolicy() {<br \/>\n    const retentionPeriods = {<br \/>\n      &#039;userActivity&#039;: 365, \/\/ 1 year<br \/>\n      &#039;transaction&#039;: 2555, \/\/ 7 years<br \/>\n      &#039;consent&#039;: 2555, \/\/ 7 years<br \/>\n      &#039;auditLog&#039;: 2555 \/\/ 7 years<br \/>\n    };<\/p>\n<p>    for (const [dataType, days] of Object.entries(retentionPeriods)) {<br \/>\n      const cutoffDate = new Date();<br \/>\n      cutoffDate.setDate(cutoffDate.getDate() &#8211; days);<\/p>\n<p>      await this.deleteExpiredData(dataType, cutoffDate);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  async deleteExpiredData(dataType, cutoffDate) {<br \/>\n    switch (dataType) {<br \/>\n      case &#039;userActivity&#039;:<br \/>\n        await UserActivity.deleteMany({<br \/>\n          timestamp: { $lt: cutoffDate }<br \/>\n        });<br \/>\n        break;<br \/>\n      case &#039;transaction&#039;:<br \/>\n        await Transaction.deleteMany({<br \/>\n          createdAt: { $lt: cutoffDate }<br \/>\n        });<br \/>\n        break;<br \/>\n      \/\/ Implement other data types<br \/>\n    }<br \/>\n  }<\/p>\n<p>  \/\/ Data access logging<br \/>\n  async logDataAccess(userId, dataType, action, purpose) {<br \/>\n    const logEntry = {<br \/>\n      userId,<br \/>\n      dataType,<br \/>\n      action, \/\/ &#039;read&#039;, &#039;write&#039;, &#039;delete&#039;<br \/>\n      purpose,<br \/>\n      timestamp: new Date(),<br \/>\n      ipAddress: this.getCurrentIP(),<br \/>\n      userAgent: this.getCurrentUserAgent()<br \/>\n    };<\/p>\n<p>    await DataAccessLog.create(logEntry);<br \/>\n  }<\/p>\n<p>  \/\/ Data breach notification<br \/>\n  async handleDataBreach(breachData) {<br \/>\n    \/\/ Log breach<br \/>\n    await SecurityBreach.create({<br \/>\n      &#8230;breachData,<br \/>\n      reportedAt: new Date(),<br \/>\n      status: &#039;investigating&#039;<br \/>\n    });<\/p>\n<p>    \/\/ Notify affected users<br \/>\n    const affectedUsers = await this.identifyAffectedUsers(breachData);<br \/>\n    await this.notifyAffectedUsers(affectedUsers, breachData);<\/p>\n<p>    \/\/ Notify authorities if required<br \/>\n    if (breachData.severity === &#039;high&#039;) {<br \/>\n      await this.notifyAuthorities(breachData);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  async identifyAffectedUsers(breachData) {<br \/>\n    \/\/ Logic to identify users whose data was compromised<br \/>\n    return User.find({<br \/>\n      $or: [<br \/>\n        { email: { $in: breachData.compromisedEmails } },<br \/>\n        { phone: { $in: breachData.compromisedPhones } }<br \/>\n      ]<br \/>\n    });<br \/>\n  }<\/p>\n<p>  async notifyAffectedUsers(users, breachData) {<br \/>\n    for (const user of users) {<br \/>\n      await EmailService.sendDataBreachNotification(user.email, {<br \/>\n        breachType: breachData.type,<br \/>\n        compromisedData: breachData.compromisedDataTypes,<br \/>\n        recommendedActions: [<br \/>\n          &#039;Change password immediately&#039;,<br \/>\n          &#039;Monitor account activity&#039;,<br \/>\n          &#039;Report suspicious activity&#039;<br \/>\n        ]<br \/>\n      });<br \/>\n    }<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ Data Classification dan Handling<br \/>\nclass DataClassification {<br \/>\n  constructor() {<br \/>\n    this.classifications = {<br \/>\n      &#039;public&#039;: { impact: &#039;none&#039;, encryption: false, accessLevel: &#039;all&#039; },<br \/>\n      &#039;internal&#039;: { impact: &#039;low&#039;, encryption: false, accessLevel: &#039;employee&#039; },<br \/>\n      &#039;confidential&#039;: { impact: &#039;medium&#039;, encryption: true, accessLevel: &#039;authorized&#039; },<br \/>\n      &#039;restricted&#039;: { impact: &#039;high&#039;, encryption: true, accessLevel: &#039;need-to-know&#039; }<br \/>\n    };<br \/>\n  }<\/p>\n<p>  classifyData(dataType, content) {<br \/>\n    \/\/ Auto-classification logic<br \/>\n    const patterns = {<br \/>\n      &#039;nik&#039;: \/\\b\\d{16}\\b\/,<br \/>\n      &#039;email&#039;: \/\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b\/,<br \/>\n      &#039;phone&#039;: \/\\b\\d{10,13}\\b\/,<br \/>\n      &#039;creditCard&#039;: \/\\b\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b\/<br \/>\n    };<\/p>\n<p>    for (const [type, pattern] of Object.entries(patterns)) {<br \/>\n      if (pattern.test(content)) {<br \/>\n        if ([&#039;nik&#039;, &#039;creditCard&#039;].includes(type)) {<br \/>\n          return &#039;restricted&#039;;<br \/>\n        }<br \/>\n        return &#039;confidential&#039;;<br \/>\n      }<br \/>\n    }<\/p>\n<p>    \/\/ Default classification<br \/>\n    return &#039;internal&#039;;<br \/>\n  }<\/p>\n<p>  applyProtection(data, classification) {<br \/>\n    const rules = this.classifications[classification];<\/p>\n<p>    if (!rules) {<br \/>\n      throw new Error(`Unknown classification: ${classification}`);<br \/>\n    }<\/p>\n<p>    return {<br \/>\n      &#8230;data,<br \/>\n      classification,<br \/>\n      accessLevel: rules.accessLevel,<br \/>\n      encrypted: rules.encryption,<br \/>\n      lastAccessed: new Date()<br \/>\n    };<br \/>\n  }<br \/>\n}<br \/>\n&#8220;`<\/p>\n<p>Kesimpulan<\/p>\n<p>Web application security adalah ongoing process yang membutuhkan continuous attention dan improvement. Dengan implementasi comprehensive security measures dari development phase hingga production monitoring, kita dapat protect applications dari modern cyber threats.<\/p>\n<p>Key security priorities untuk 2025:<br \/>\n\u2022 Zero Trust Architecture: Never trust, always verify<br \/>\n\u2022 AI-Powered Security: Leverage machine learning untuk threat detection<br \/>\n\u2022 DevSecOps Integration: Security embedded dalam development lifecycle<br \/>\n\u2022 Compliance Management: Adherence to evolving regulations<br \/>\n\u2022 User Education: Security awareness training untuk semua stakeholders<\/p>\n<p>Investasi dalam security bukan optional melainkan necessity di digital age. Cost prevention jauh lebih rendah dari cost recovery setelah security breach terjadi.<\/p>\n<p>Remember: Security is everyone&#039;s responsibility. Build security culture dalam organization, stay updated dengan latest threats, dan continuously improve security practices.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Di era digital di mana cyber threats semakin sophisticated, web application security menjadi prioritas utama untuk developer dan bisnis. Data breaches dapat menyebabkan kerugian finansial yang signifikan, kehilangan kepercayaan customer, dan konsekuensi legal yang serius. Artikel ini akan memberikan comprehensive guide tentang web application security best practices di tahun 2025. Understanding Modern Cybersecurity Landscape 1. [&hellip;]<\/p>\n","protected":false},"author":6,"featured_media":4421,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.8 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Cybersecurity: Web Application Security Best Practices Complete Guide - Demo Website<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Cybersecurity: Web Application Security Best Practices Complete Guide - Demo Website\" \/>\n<meta property=\"og:description\" content=\"Di era digital di mana cyber threats semakin sophisticated, web application security menjadi prioritas utama untuk developer dan bisnis. Data breaches dapat menyebabkan kerugian finansial yang signifikan, kehilangan kepercayaan customer, dan konsekuensi legal yang serius. Artikel ini akan memberikan comprehensive guide tentang web application security best practices di tahun 2025. Understanding Modern Cybersecurity Landscape 1. [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/\" \/>\n<meta property=\"og:site_name\" content=\"Demo Website\" \/>\n<meta property=\"article:published_time\" content=\"2025-11-11T01:21:30+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/www.jagowebdesign.com\/website\/wp-content\/uploads\/2025\/11\/website-8.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1024\" \/>\n\t<meta property=\"og:image:height\" content=\"1024\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"redakturjagowebdesign\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"redakturjagowebdesign\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/\",\"url\":\"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/\",\"name\":\"Cybersecurity: Web Application Security Best Practices Complete Guide - Demo Website\",\"isPartOf\":{\"@id\":\"https:\/\/www.jagowebdesign.com\/website\/#website\"},\"datePublished\":\"2025-11-11T01:21:30+00:00\",\"dateModified\":\"2025-11-11T01:21:30+00:00\",\"author\":{\"@id\":\"https:\/\/www.jagowebdesign.com\/website\/#\/schema\/person\/9c4f0b34abafcb25285cfc51e9459095\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.jagowebdesign.com\/website\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Cybersecurity: Web Application Security Best Practices Complete Guide\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.jagowebdesign.com\/website\/#website\",\"url\":\"https:\/\/www.jagowebdesign.com\/website\/\",\"name\":\"Demo Website\",\"description\":\"Jagowebdesign.Com\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.jagowebdesign.com\/website\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.jagowebdesign.com\/website\/#\/schema\/person\/9c4f0b34abafcb25285cfc51e9459095\",\"name\":\"redakturjagowebdesign\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.jagowebdesign.com\/website\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/cf55dfe07e97818622d2a46e2c6de4b1?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/cf55dfe07e97818622d2a46e2c6de4b1?s=96&d=mm&r=g\",\"caption\":\"redakturjagowebdesign\"},\"url\":\"https:\/\/www.jagowebdesign.com\/website\/author\/redakturjagowebdesign\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Cybersecurity: Web Application Security Best Practices Complete Guide - Demo Website","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/","og_locale":"en_US","og_type":"article","og_title":"Cybersecurity: Web Application Security Best Practices Complete Guide - Demo Website","og_description":"Di era digital di mana cyber threats semakin sophisticated, web application security menjadi prioritas utama untuk developer dan bisnis. Data breaches dapat menyebabkan kerugian finansial yang signifikan, kehilangan kepercayaan customer, dan konsekuensi legal yang serius. Artikel ini akan memberikan comprehensive guide tentang web application security best practices di tahun 2025. Understanding Modern Cybersecurity Landscape 1. [&hellip;]","og_url":"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/","og_site_name":"Demo Website","article_published_time":"2025-11-11T01:21:30+00:00","og_image":[{"width":1024,"height":1024,"url":"http:\/\/www.jagowebdesign.com\/website\/wp-content\/uploads\/2025\/11\/website-8.jpg","type":"image\/jpeg"}],"author":"redakturjagowebdesign","twitter_card":"summary_large_image","twitter_misc":{"Written by":"redakturjagowebdesign"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/","url":"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/","name":"Cybersecurity: Web Application Security Best Practices Complete Guide - Demo Website","isPartOf":{"@id":"https:\/\/www.jagowebdesign.com\/website\/#website"},"datePublished":"2025-11-11T01:21:30+00:00","dateModified":"2025-11-11T01:21:30+00:00","author":{"@id":"https:\/\/www.jagowebdesign.com\/website\/#\/schema\/person\/9c4f0b34abafcb25285cfc51e9459095"},"breadcrumb":{"@id":"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.jagowebdesign.com\/website\/cybersecurity-web-application-security-best-practices-complete-guide\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.jagowebdesign.com\/website\/"},{"@type":"ListItem","position":2,"name":"Cybersecurity: Web Application Security Best Practices Complete Guide"}]},{"@type":"WebSite","@id":"https:\/\/www.jagowebdesign.com\/website\/#website","url":"https:\/\/www.jagowebdesign.com\/website\/","name":"Demo Website","description":"Jagowebdesign.Com","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.jagowebdesign.com\/website\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.jagowebdesign.com\/website\/#\/schema\/person\/9c4f0b34abafcb25285cfc51e9459095","name":"redakturjagowebdesign","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.jagowebdesign.com\/website\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/cf55dfe07e97818622d2a46e2c6de4b1?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/cf55dfe07e97818622d2a46e2c6de4b1?s=96&d=mm&r=g","caption":"redakturjagowebdesign"},"url":"https:\/\/www.jagowebdesign.com\/website\/author\/redakturjagowebdesign\/"}]}},"_links":{"self":[{"href":"https:\/\/www.jagowebdesign.com\/website\/wp-json\/wp\/v2\/posts\/4444"}],"collection":[{"href":"https:\/\/www.jagowebdesign.com\/website\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.jagowebdesign.com\/website\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.jagowebdesign.com\/website\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/www.jagowebdesign.com\/website\/wp-json\/wp\/v2\/comments?post=4444"}],"version-history":[{"count":1,"href":"https:\/\/www.jagowebdesign.com\/website\/wp-json\/wp\/v2\/posts\/4444\/revisions"}],"predecessor-version":[{"id":4445,"href":"https:\/\/www.jagowebdesign.com\/website\/wp-json\/wp\/v2\/posts\/4444\/revisions\/4445"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.jagowebdesign.com\/website\/wp-json\/wp\/v2\/media\/4421"}],"wp:attachment":[{"href":"https:\/\/www.jagowebdesign.com\/website\/wp-json\/wp\/v2\/media?parent=4444"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.jagowebdesign.com\/website\/wp-json\/wp\/v2\/categories?post=4444"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.jagowebdesign.com\/website\/wp-json\/wp\/v2\/tags?post=4444"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}