Secure API Implementation Guide for Vibe Coding


Introduction

APIs (Application Programming Interfaces) serve as the critical communication channels between different components of modern applications, enabling data exchange, integration with third-party services, and separation of frontend and backend concerns. In the rapidly evolving landscape of vibe coding, where AI tools generate code based on natural language prompts, API security often receives inadequate attention, leading to significant vulnerabilities that malicious actors can exploit to compromise data integrity, confidentiality, and system availability.

According to a recent report by Salt Security, API attacks increased by 681% in 2022 alone, with 94% of organizations experiencing some form of API security incident. This alarming trend highlights the critical importance of implementing secure API practices in all applications, especially those built with vibe coding tools where security considerations might be overlooked in favor of rapid development.

When developers instruct AI to “create an API endpoint for user data” or “build a payment processing API,” the resulting code often implements basic functionality without incorporating crucial security measures like proper authentication, input validation, rate limiting, or encryption. These omissions create significant security gaps that can lead to data breaches, unauthorized access, and service disruptions.

This article provides a comprehensive guide to implementing secure APIs in vibe-coded applications built with popular full-stack app builders like Lovable.dev, Bolt.new, Tempo Labs, Base44, and Replit. We’ll examine common API security vulnerabilities in AI-generated code, demonstrate secure implementation approaches for each platform, and provide practical techniques for enhancing API security. By following these practices, you can ensure that your vibe-coded applications maintain robust protection against API-related threats while still benefiting from the rapid development that AI tools enable.

Common API Security Vulnerabilities in AI-Generated Code

AI coding assistants typically follow certain patterns when generating API endpoints. Understanding these patterns and their associated vulnerabilities is the first step toward implementing secure APIs.

Insufficient Authentication and Authorization

One of the most common and dangerous vulnerabilities in AI-generated API code is inadequate authentication and authorization:

// Example of insecure API endpoint in AI-generated code
app.get('/api/users', (req, res) => {
  // No authentication check
  // No authorization verification
  
  // Fetch all users from database
  db.query('SELECT * FROM users', (err, results) => {
    if (err) {
      return res.status(500).json({ error: 'Database error' });
    }
    
    // Return sensitive user data
    res.json(results);
  });
});

This approach creates several security risks:

  • Unauthenticated access to sensitive user data
  • No verification of user permissions or roles
  • No protection against unauthorized data access
  • Potential exposure of sensitive personal information

Lack of Input Validation

AI-generated API endpoints often lack proper input validation:

// Example of API endpoint without input validation
app.post('/api/products', (req, res) => {
  const { name, price, description, category } = req.body;
  
  // No validation of inputs
  // No sanitization of data
  
  // Insert product into database
  db.query(
    'INSERT INTO products (name, price, description, category) VALUES (?, ?, ?, ?)',
    [name, price, description, category],
    (err, result) => {
      if (err) {
        return res.status(500).json({ error: 'Failed to create product' });
      }
      
      res.status(201).json({ id: result.insertId, message: 'Product created' });
    }
  );
});

This code is vulnerable because:

  • No validation of data types or formats
  • No checks for required fields
  • No sanitization of inputs
  • No protection against malicious payloads

Missing Rate Limiting

AI tools rarely include rate limiting in generated API code:

// Example of API endpoint without rate limiting
app.post('/api/login', async (req, res) => {
  const { email, password } = req.body;
  
  try {
    // Find user by email
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(401).json({ message: 'Invalid credentials' });
    }
    
    // Check password
    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) {
      return res.status(401).json({ message: 'Invalid credentials' });
    }
    
    // Generate token
    const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, {
      expiresIn: '1h'
    });
    
    res.json({ token });
  } catch (error) {
    res.status(500).json({ message: 'Server error' });
  }
});

This endpoint is vulnerable to:

  • Brute force attacks
  • Credential stuffing attacks
  • Denial of service (DoS) attacks
  • Resource exhaustion

Insecure Error Handling

AI-generated code often includes insecure error handling that leaks sensitive information:

// Example of insecure error handling
app.get('/api/user/:id', (req, res) => {
  const userId = req.params.id;
  
  db.query('SELECT * FROM users WHERE id = ?', [userId], (err, results) => {
    if (err) {
      // Vulnerable: Returning detailed error information
      return res.status(500).json({ 
        error: 'Database error', 
        details: err.message,
        stack: err.stack,
        query: err.sql
      });
    }
    
    if (results.length === 0) {
      return res.status(404).json({ error: 'User not found' });
    }
    
    res.json(results[0]);
  });
});

This approach leaks sensitive information:

  • Database error messages that reveal implementation details
  • Stack traces that expose code structure
  • SQL queries that reveal database schema
  • Information that could aid attackers in exploiting vulnerabilities

Lack of HTTPS Enforcement

AI tools often generate code without HTTPS enforcement:

// Example of server setup without HTTPS enforcement
const express = require('express');
const app = express();

// Middleware setup
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// API routes
app.use('/api', apiRoutes);

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

This code is problematic because:

  • No redirection from HTTP to HTTPS
  • No Strict-Transport-Security header
  • Data transmitted in plaintext could be intercepted
  • Vulnerable to man-in-the-middle attacks

Missing CORS Configuration

AI-generated API code often lacks proper CORS (Cross-Origin Resource Sharing) configuration:

// Example of missing or overly permissive CORS configuration
const express = require('express');
const cors = require('cors');
const app = express();

// Vulnerable: Allowing all origins
app.use(cors());

// API routes
app.use('/api', apiRoutes);

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

This configuration is insecure because:

  • It allows requests from any origin
  • No restrictions on methods or headers
  • Increases vulnerability to cross-site request forgery (CSRF)
  • Enables malicious sites to interact with your API

Platform-Specific API Security Vulnerabilities

Each full-stack app builder has its own patterns of API implementation. Let’s examine specific examples from each platform.

Lovable.dev

Lovable.dev integrates with Supabase for backend functionality, but AI-generated code might not implement proper security measures:

// Lovable.dev vulnerable API implementation
import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs';

// API route for fetching user data
export default async function handler(req, res) {
  // Create Supabase client
  const supabase = createServerSupabaseClient({ req, res });
  
  // Get user data
  const { data: userData, error: userError } = await supabase
    .from('profiles')
    .select('*');
  
  if (userError) {
    // Vulnerable: Returning detailed error information
    return res.status(500).json({ error: userError });
  }
  
  // Vulnerable: No filtering of sensitive data
  return res.status(200).json(userData);
}

The issues here include:

  • No proper authorization checks for data access
  • Returning all user profiles regardless of the requester’s role
  • Exposing detailed error information
  • No rate limiting to prevent abuse

Bolt.new

Bolt.new might generate Next.js API routes with security vulnerabilities:

// Bolt.new vulnerable API implementation
import type { NextApiRequest, NextApiResponse } from 'next';
import { supabase } from '../../lib/supabaseClient';

type Data = {
  products: any[];
} | {
  error: string;
  details?: any;
};

// API route for product filtering
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  // Vulnerable: No authentication check
  
  const { category, minPrice, maxPrice } = req.query;
  
  try {
    let query = supabase.from('products').select('*');
    
    // Apply filters
    if (category) {
      // Vulnerable: No validation of category
      query = query.eq('category', category);
    }
    
    if (minPrice) {
      // Vulnerable: No validation of minPrice
      query = query.gte('price', minPrice);
    }
    
    if (maxPrice) {
      // Vulnerable: No validation of maxPrice
      query = query.lte('price', maxPrice);
    }
    
    const { data, error } = await query;
    
    if (error) {
      // Vulnerable: Returning detailed error information
      return res.status(500).json({ error: 'Database error', details: error });
    }
    
    return res.status(200).json({ products: data });
  } catch (error) {
    // Vulnerable: Returning detailed error information
    return res.status(500).json({ error: 'Server error', details: error });
  }
}

The vulnerabilities include:

  • No authentication or authorization checks
  • No input validation for query parameters
  • Exposing detailed error information
  • No rate limiting to prevent abuse

Tempo Labs

Tempo Labs might generate API code with insufficient security measures:

// Tempo Labs vulnerable API implementation
import { PrismaClient } from '@prisma/client';
import { verify } from 'jsonwebtoken';

const prisma = new PrismaClient();

// API route for user operations
export default async function handler(req, res) {
  // Basic authentication check
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  
  const token = authHeader.split(' ')[1];
  
  try {
    // Verify token
    const decoded = verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    
    // Handle different HTTP methods
    switch (req.method) {
      case 'GET':
        return getUserData(req, res);
      case 'PUT':
        return updateUserData(req, res);
      case 'DELETE':
        return deleteUser(req, res);
      default:
        return res.status(405).json({ error: 'Method not allowed' });
    }
  } catch (error) {
    // Vulnerable: Returning token verification errors
    return res.status(401).json({ error: 'Invalid token', details: error.message });
  }
}

// Get user data
async function getUserData(req, res) {
  try {
    // Vulnerable: No role-based access control
    const users = await prisma.user.findMany({
      include: {
        profile: true,
        orders: true
      }
    });
    
    // Vulnerable: Returning all user data
    return res.status(200).json(users);
  } catch (error) {
    // Vulnerable: Returning detailed error information
    return res.status(500).json({ error: 'Database error', details: error.message });
  }
}

// Update user data
async function updateUserData(req, res) {
  const { id, ...data } = req.body;
  
  // Vulnerable: No validation of input data
  
  try {
    const updatedUser = await prisma.user.update({
      where: { id },
      data
    });
    
    return res.status(200).json(updatedUser);
  } catch (error) {
    // Vulnerable: Returning detailed error information
    return res.status(500).json({ error: 'Update failed', details: error.message });
  }
}

// Delete user
async function deleteUser(req, res) {
  const { id } = req.body;
  
  // Vulnerable: No additional authorization check for deletion
  
  try {
    await prisma.user.delete({
      where: { id }
    });
    
    return res.status(200).json({ message: 'User deleted' });
  } catch (error) {
    // Vulnerable: Returning detailed error information
    return res.status(500).json({ error: 'Deletion failed', details: error.message });
  }
}

The security issues include:

  • No role-based access control
  • No validation of input data
  • Returning all user data regardless of the requester’s role
  • Exposing detailed error information
  • No rate limiting to prevent abuse

Base44

Base44 often generates more bare-bones API code with significant security gaps:

// Base44 vulnerable API implementation
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const User = require('../models/User');

// Middleware to verify token
function verifyToken(req, res, next) {
  const token = req.header('auth-token');
  if (!token) return res.status(401).json({ error: 'Access denied' });
  
  try {
    const verified = jwt.verify(token, 'your_jwt_secret');
    req.user = verified;
    next();
  } catch (error) {
    res.status(400).json({ error: 'Invalid token' });
  }
}

// Get all users
router.get('/users', verifyToken, async (req, res) => {
  // Vulnerable: No role check for accessing all users
  
  try {
    const users = await User.find();
    res.json(users);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Get user by ID
router.get('/users/:id', verifyToken, async (req, res) => {
  // Vulnerable: No check if user is requesting their own data
  
  try {
    const user = await User.findById(req.params.id);
    if (!user) return res.status(404).json({ error: 'User not found' });
    
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Create new user
router.post('/users', async (req, res) => {
  // Vulnerable: No input validation
  
  const user = new User({
    name: req.body.name,
    email: req.body.email,
    password: req.body.password
  });
  
  try {
    const savedUser = await user.save();
    res.status(201).json(savedUser);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// Update user
router.put('/users/:id', verifyToken, async (req, res) => {
  // Vulnerable: No check if user is updating their own data
  // Vulnerable: No input validation
  
  try {
    const updatedUser = await User.findByIdAndUpdate(
      req.params.id,
      req.body,
      { new: true }
    );
    
    if (!updatedUser) return res.status(404).json({ error: 'User not found' });
    
    res.json(updatedUser);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// Delete user
router.delete('/users/:id', verifyToken, async (req, res) => {
  // Vulnerable: No check if user is deleting their own data or has admin rights
  
  try {
    const removedUser = await User.findByIdAndDelete(req.params.id);
    if (!removedUser) return res.status(404).json({ error: 'User not found' });
    
    res.json({ message: 'User deleted' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

module.exports = router;

The security issues include:

  • Hardcoded JWT secret
  • No role-based access control
  • No input validation
  • No rate limiting
  • Exposing detailed error messages

Replit

Replit’s AI assistant often generates Python API code with security weaknesses:

# Replit vulnerable API implementation
from flask import Flask, request, jsonify
import jwt
from functools import wraps
import sqlite3

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'

# Database connection
def get_db_connection():
    conn = sqlite3.connect('database.db')
    conn.row_factory = sqlite3.Row
    return conn

# Token verification decorator
def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization')
        
        if not token:
            return jsonify({'error': 'Token is missing'}), 401
        
        try:
            # Vulnerable: Using a hardcoded secret key
            data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
            # Vulnerable: Not checking if the user exists or is active
        except:
            return jsonify({'error': 'Token is invalid'}), 401
        
        return f(*args, **kwargs)
    
    return decorated

# Get all users
@app.route('/api/users', methods=['GET'])
@token_required
def get_users():
    # Vulnerable: No role check for accessing all users
    
    conn = get_db_connection()
    users = conn.execute('SELECT * FROM users').fetchall()
    conn.close()
    
    # Vulnerable: Converting all user data including passwords
    return jsonify([dict(user) for user in users])

# Get user by ID
@app.route('/api/users/<int:user_id>', methods=['GET'])
@token_required
def get_user(user_id):
    # Vulnerable: No check if user is requesting their own data
    
    conn = get_db_connection()
    user = conn.execute('SELECT * FROM users WHERE id = ?', (user_id,)).fetchone()
    conn.close()
    
    if not user:
        return jsonify({'error': 'User not found'}), 404
    
    # Vulnerable: Returning all user data including password
    return jsonify(dict(user))

# Create new user
@app.route('/api/users', methods=['POST'])
def create_user():
    # Vulnerable: No input validation
    
    data = request.get_json()
    
    if not data or not 'email' in data or not 'password' in data:
        return jsonify({'error': 'Missing required fields'}), 400
    
    conn = get_db_connection()
    
    # Vulnerable: Storing password without hashing
    conn.execute(
        'INSERT INTO users (name, email, password) VALUES (?, ?, ?)',
        (data.get('name'), data['email'], data['password'])
    )
    
    conn.commit()
    user_id = conn.execute('SELECT last_insert_rowid()').fetchone()[0]
    conn.close()
    
    return jsonify({'id': user_id, 'message': 'User created successfully'}), 201

# Update user
@app.route('/api/users/<int:user_id>', methods=['PUT'])
@token_required
def update_user(user_id):
    # Vulnerable: No check if user is updating their own data
    # Vulnerable: No input validation
    
    data = request.get_json()
    
    conn = get_db_connection()
    user = conn.execute('SELECT * FROM users WHERE id = ?', (user_id,)).fetchone()
    
    if not user:
        conn.close()
        return jsonify({'error': 'User not found'}), 404
    
    # Vulnerable: Updating all fields without validation
    name = data.get('name', user['name'])
    email = data.get('email', user['email'])
    password = data.get('password', user['password'])
    
    conn.execute(
        'UPDATE users SET name = ?, email = ?, password = ? WHERE id = ?',
        (name, email, password, user_id)
    )
    
    conn.commit()
    conn.close()
    
    return jsonify({'message': 'User updated successfully'})

# Delete user
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
@token_required
def delete_user(user_id):
    # Vulnerable: No check if user is deleting their own data or has admin rights
    
    conn = get_db_connection()
    user = conn.execute('SELECT * FROM users WHERE id = ?', (user_id,)).fetchone()
    
    if not user:
        conn.close()
        return jsonify({'error': 'User not found'}), 404
    
    conn.execute('DELETE FROM users WHERE id = ?', (user_id,))
    conn.commit()
    conn.close()
    
    return jsonify({'message': 'User deleted successfully'})

if __name__ == '__main__':
    app.run(debug=True)

The security issues include:

  • Hardcoded secret key
  • Storing passwords without hashing
  • No role-based access control
  • No input validation
  • Exposing sensitive user data
  • Running with debug mode enabled

Secure API Implementation Techniques

Now that we’ve identified common vulnerabilities, let’s explore secure implementation techniques for APIs in vibe-coded applications.

Proper Authentication and Authorization

Implement robust authentication and authorization mechanisms:

// Secure authentication middleware
const jwt = require('jsonwebtoken');

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'Access token required' });
  }
  
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({ error: 'Invalid or expired token' });
    }
    
    req.user = user;
    next();
  });
}

// Role-based authorization middleware
function authorizeRole(roles) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }
    
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    
    next();
  };
}

// Secure API endpoint with authentication and authorization
app.get('/api/users', 
  authenticateToken, 
  authorizeRole(['admin']), 
  (req, res) => {
    // Only admins can access this endpoint
    db.query('SELECT id, username, email FROM users', (err, results) => {
      if (err) {
        return res.status(500).json({ error: 'Internal server error' });
      }
      
      res.json(results);
    });
  }
);

// Endpoint with owner-based authorization
app.get('/api/users/:id', 
  authenticateToken, 
  (req, res) => {
    const userId = req.params.id;
    
    // Check if user is requesting their own data or is an admin
    if (req.user.id !== userId && req.user.role !== 'admin') {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    
    db.query('SELECT id, username, email FROM users WHERE id = ?', 
      [userId], 
      (err, results) => {
        if (err) {
          return res.status(500).json({ error: 'Internal server error' });
        }
        
        if (results.length === 0) {
          return res.status(404).json({ error: 'User not found' });
        }
        
        res.json(results[0]);
      }
    );
  }
);

This approach:

  • Verifies the authenticity of requests using JWT
  • Implements role-based access control
  • Enforces owner-based authorization for personal data
  • Prevents unauthorized access to sensitive information

Comprehensive Input Validation

Implement thorough input validation for all API endpoints:

// Secure input validation with express-validator
const { body, param, query, validationResult } = require('express-validator');

// Create product endpoint with validation
app.post('/api/products',
  authenticateToken,
  authorizeRole(['admin', 'editor']),
  [
    // Validate request body
    body('name')
      .trim()
      .isLength({ min: 2, max: 100 })
      .withMessage('Product name must be between 2 and 100 characters'),
    
    body('price')
      .isNumeric()
      .withMessage('Price must be a number')
      .custom(value => value >= 0)
      .withMessage('Price cannot be negative'),
    
    body('description')
      .trim()
      .isLength({ max: 1000 })
      .withMessage('Description cannot exceed 1000 characters'),
    
    body('category')
      .trim()
      .isLength({ min: 2, max: 50 })
      .withMessage('Category must be between 2 and 50 characters')
  ],
  (req, res) => {
    // Check for validation errors
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    
    const { name, price, description, category } = req.body;
    
    // Insert validated product into database
    db.query(
      'INSERT INTO products (name, price, description, category) VALUES (?, ?, ?, ?)',
      [name, price, description, category],
      (err, result) => {
        if (err) {
          return res.status(500).json({ error: 'Internal server error' });
        }
        
        res.status(201).json({ 
          id: result.insertId, 
          message: 'Product created successfully' 
        });
      }
    );
  }
);

// Search products endpoint with validation
app.get('/api/products',
  [
    // Validate query parameters
    query('category')
      .optional()
      .trim()
      .isLength({ min: 2, max: 50 })
      .withMessage('Invalid category'),
    
    query('minPrice')
      .optional()
      .isNumeric()
      .withMessage('Min price must be a number')
      .custom(value => value >= 0)
      .withMessage('Min price cannot be negative'),
    
    query('maxPrice')
      .optional()
      .isNumeric()
      .withMessage('Max price must be a number')
      .custom(value => value >= 0)
      .withMessage('Max price cannot be negative'),
    
    query('page')
      .optional()
      .isInt({ min: 1 })
      .withMessage('Page must be a positive integer'),
    
    query('limit')
      .optional()
      .isInt({ min: 1, max: 100 })
      .withMessage('Limit must be between 1 and 100')
  ],
  (req, res) => {
    // Check for validation errors
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    
    // Process validated query parameters
    const category = req.query.category;
    const minPrice = req.query.minPrice ? parseFloat(req.query.minPrice) : null;
    const maxPrice = req.query.maxPrice ? parseFloat(req.query.maxPrice) : null;
    const page = req.query.page ? parseInt(req.query.page) : 1;
    const limit = req.query.limit ? parseInt(req.query.limit) : 20;
    const offset = (page - 1) * limit;
    
    // Build query with validated parameters
    let query = 'SELECT * FROM products WHERE 1=1';
    const params = [];
    
    if (category) {
      query += ' AND category = ?';
      params.push(category);
    }
    
    if (minPrice !== null) {
      query += ' AND price >= ?';
      params.push(minPrice);
    }
    
    if (maxPrice !== null) {
      query += ' AND price <= ?';
      params.push(maxPrice);
    }
    
    query += ' LIMIT ? OFFSET ?';
    params.push(limit, offset);
    
    // Execute query with validated parameters
    db.query(query, params, (err, results) => {
      if (err) {
        return res.status(500).json({ error: 'Internal server error' });
      }
      
      res.json(results);
    });
  }
);

This approach:

  • Validates all input data before processing
  • Enforces data type, format, and range constraints
  • Provides clear error messages for invalid inputs
  • Prevents injection attacks and data corruption

Implement Rate Limiting

Add rate limiting to protect against abuse:

// Secure rate limiting implementation
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');

const redis = new Redis({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT,
  password: process.env.REDIS_PASSWORD
});

// General API rate limiter
const apiLimiter = rateLimit({
  store: new RedisStore({
    sendCommand: (...args) => redis.call(...args)
  }),
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
  legacyHeaders: false, // Disable the `X-RateLimit-*` headers
  message: { error: 'Too many requests, please try again later' }
});

// More strict rate limiter for authentication endpoints
const authLimiter = rateLimit({
  store: new RedisStore({
    sendCommand: (...args) => redis.call(...args)
  }),
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 10, // Limit each IP to 10 requests per windowMs
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many login attempts, please try again later' }
});

// Apply rate limiting to routes
app.use('/api/', apiLimiter); // Apply to all API routes
app.use('/api/login', authLimiter); // Apply stricter limits to login
app.use('/api/register', authLimiter); // Apply stricter limits to registration

// Dynamic rate limiting based on user role
function dynamicRateLimit(req, res, next) {
  const userRole = req.user ? req.user.role : 'anonymous';
  
  // Set limits based on user role
  let limit;
  switch (userRole) {
    case 'admin':
      limit = 1000;
      break;
    case 'premium':
      limit = 500;
      break;
    case 'user':
      limit = 100;
      break;
    default:
      limit = 50;
  }
  
  // Create and apply dynamic limiter
  const dynamicLimiter = rateLimit({
    store: new RedisStore({
      sendCommand: (...args) => redis.call(...args),
      prefix: `ratelimit:${userRole}:`
    }),
    windowMs: 15 * 60 * 1000,
    max: limit,
    standardHeaders: true,
    legacyHeaders: false,
    message: { error: 'Rate limit exceeded' }
  });
  
  return dynamicLimiter(req, res, next);
}

// Apply dynamic rate limiting to specific routes
app.use('/api/data', authenticateToken, dynamicRateLimit);

This approach:

  • Limits request frequency to prevent abuse
  • Applies stricter limits to sensitive endpoints
  • Uses Redis for distributed rate limiting
  • Implements dynamic limits based on user roles
  • Provides clear feedback when limits are exceeded

Secure Error Handling

Implement secure error handling that doesn’t leak sensitive information:

// Secure error handling middleware
function errorHandler(err, req, res, next) {
  // Log the error for internal debugging
  console.error('Error:', err);
  
  // Determine error type and set appropriate status code
  let statusCode = 500;
  let errorMessage = 'Internal server error';
  
  if (err.name === 'ValidationError') {
    statusCode = 400;
    errorMessage = 'Invalid input data';
  } else if (err.name === 'UnauthorizedError') {
    statusCode = 401;
    errorMessage = 'Authentication required';
  } else if (err.name === 'ForbiddenError') {
    statusCode = 403;
    errorMessage = 'Insufficient permissions';
  } else if (err.name === 'NotFoundError') {
    statusCode = 404;
    errorMessage = 'Resource not found';
  }
  
  // Send generic error response without sensitive details
  res.status(statusCode).json({ error: errorMessage });
}

// Custom error classes
class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ValidationError';
  }
}

class UnauthorizedError extends Error {
  constructor(message) {
    super(message);
    this.name = 'UnauthorizedError';
  }
}

class ForbiddenError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ForbiddenError';
  }
}

class NotFoundError extends Error {
  constructor(message) {
    super(message);
    this.name = 'NotFoundError';
  }
}

// Apply error handling middleware
app.use(errorHandler);

// Example API endpoint with secure error handling
app.get('/api/users/:id', authenticateToken, async (req, res, next) => {
  try {
    const userId = req.params.id;
    
    // Check if user is requesting their own data or is an admin
    if (req.user.id !== userId && req.user.role !== 'admin') {
      throw new ForbiddenError('Cannot access other user data');
    }
    
    const user = await User.findById(userId);
    
    if (!user) {
      throw new NotFoundError('User not found');
    }
    
    // Return only necessary user data
    res.json({
      id: user.id,
      username: user.username,
      email: user.email,
      createdAt: user.createdAt
    });
  } catch (error) {
    // Pass error to error handling middleware
    next(error);
  }
});

This approach:

  • Provides generic error messages to clients
  • Logs detailed errors for internal debugging
  • Uses appropriate HTTP status codes
  • Prevents leakage of sensitive information
  • Maintains a consistent error response format

Enforce HTTPS

Ensure all API traffic uses HTTPS:

// Secure HTTPS enforcement middleware
function requireHTTPS(req, res, next) {
  // The 'x-forwarded-proto' header is used by load balancers
  if (
    !req.secure && 
    req.get('x-forwarded-proto') !== 'https' && 
    process.env.NODE_ENV !== 'development'
  ) {
    return res.redirect('https://' + req.get('host') + req.url);
  }
  next();
}

// Apply HTTPS middleware
app.use(requireHTTPS);

// Set security headers
app.use((req, res, next) => {
  // Strict Transport Security
  res.setHeader(
    'Strict-Transport-Security',
    'max-age=31536000; includeSubDomains; preload'
  );
  
  // Other security headers
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  
  next();
});

This approach:

  • Redirects HTTP requests to HTTPS
  • Sets Strict-Transport-Security header to enforce HTTPS
  • Adds additional security headers
  • Prevents man-in-the-middle attacks
  • Ensures data is transmitted securely

Proper CORS Configuration

Implement secure CORS configuration:

// Secure CORS configuration
const cors = require('cors');

// Define allowed origins
const allowedOrigins = [
  'https://yourapplication.com',
  'https://admin.yourapplication.com',
  'https://api.yourapplication.com'
];

// Configure CORS options
const corsOptions = {
  origin: function (origin, callback) {
    // Allow requests with no origin (like mobile apps, curl, etc.)
    if (!origin) return callback(null, true);
    
    if (allowedOrigins.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400 // 24 hours
};

// Apply CORS middleware
app.use(cors(corsOptions));

// Handle CORS errors
app.use((err, req, res, next) => {
  if (err.message === 'Not allowed by CORS') {
    return res.status(403).json({ error: 'Cross-origin request not allowed' });
  }
  next(err);
});

This approach:

  • Restricts access to specific origins
  • Limits allowed HTTP methods
  • Specifies allowed headers
  • Enables credentials for authenticated requests
  • Caches preflight requests to improve performance

Platform-Specific Secure API Implementations

Let’s look at secure implementations for each full-stack app builder.

Lovable.dev Secure Implementation

For Lovable.dev’s Supabase-based applications:

// Secure Lovable.dev API implementation
import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs';

// API route for fetching user data
export default async function handler(req, res) {
  try {
    // Create Supabase client with auth context
    const supabase = createServerSupabaseClient({ req, res });
    
    // Check if user is authenticated
    const { data: { session }, error: authError } = await supabase.auth.getSession();
    
    if (authError || !session) {
      return res.status(401).json({ error: 'Authentication required' });
    }
    
    // Get current user's profile
    const { data: profile, error: profileError } = await supabase
      .from('profiles')
      .select('id, username, avatar_url, created_at')
      .eq('id', session.user.id)
      .single();
    
    if (profileError) {
      return res.status(500).json({ error: 'Failed to fetch profile' });
    }
    
    // Check if user is an admin for additional data
    const { data: roleData, error: roleError } = await supabase
      .from('user_roles')
      .select('role')
      .eq('user_id', session.user.id)
      .single();
    
    if (roleError && roleError.code !== 'PGRST116') { // Not found error
      return res.status(500).json({ error: 'Failed to fetch user role' });
    }
    
    const isAdmin = roleData?.role === 'admin';
    
    // If admin, fetch all profiles with limited fields
    if (isAdmin && req.query.all === 'true') {
      const { data: allProfiles, error: allProfilesError } = await supabase
        .from('profiles')
        .select('id, username, created_at')
        .order('created_at', { ascending: false });
      
      if (allProfilesError) {
        return res.status(500).json({ error: 'Failed to fetch profiles' });
      }
      
      return res.status(200).json({ 
        currentUser: profile,
        isAdmin,
        allProfiles 
      });
    }
    
    // Regular user only gets their own profile
    return res.status(200).json({ 
      currentUser: profile,
      isAdmin
    });
  } catch (error) {
    console.error('API error:', error);
    return res.status(500).json({ error: 'Internal server error' });
  }
}

This implementation:

  • Uses Supabase’s authentication system properly
  • Implements role-based access control
  • Returns only necessary data fields
  • Provides generic error messages
  • Handles errors gracefully

Bolt.new Secure Implementation

For Bolt.new’s Next.js applications:

// Secure Bolt.new API implementation
import type { NextApiRequest, NextApiResponse } from 'next';
import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs';
import { z } from 'zod';

// Define response types
type SuccessResponse = {
  products: Product[];
  pagination: {
    page: number;
    limit: number;
    total: number;
    pages: number;
  };
};

type ErrorResponse = {
  error: string;
};

// Define product schema for validation
const QuerySchema = z.object({
  category: z.string().min(2).max(50).optional(),
  minPrice: z.coerce.number().min(0).optional(),
  maxPrice: z.coerce.number().min(0).optional(),
  page: z.coerce.number().int().min(1).default(1),
  limit: z.coerce.number().int().min(1).max(100).default(20)
});

// Rate limiting (simplified example - in production use Redis)
const rateLimits = new Map<string, { count: number, timestamp: number }>();

function checkRateLimit(ip: string, limit: number, windowMs: number): boolean {
  const now = Date.now();
  const record = rateLimits.get(ip);
  
  if (!record) {
    rateLimits.set(ip, { count: 1, timestamp: now });
    return true;
  }
  
  if (now - record.timestamp > windowMs) {
    // Reset if window has passed
    rateLimits.set(ip, { count: 1, timestamp: now });
    return true;
  }
  
  if (record.count >= limit) {
    return false; // Rate limit exceeded
  }
  
  // Increment count
  record.count += 1;
  rateLimits.set(ip, record);
  return true;
}

// API route for product filtering
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<SuccessResponse | ErrorResponse>
) {
  // Check rate limit (20 requests per minute)
  const clientIp = req.headers['x-forwarded-for'] as string || 'unknown';
  if (!checkRateLimit(clientIp, 20, 60 * 1000)) {
    return res.status(429).json({ error: 'Too many requests, please try again later' });
  }
  
  // Only allow GET method
  if (req.method !== 'GET') {
    return res.status(405).json({ error: 'Method not allowed' });
  }
  
  try {
    // Create Supabase client
    const supabase = createServerSupabaseClient({ req, res });
    
    // Validate query parameters
    const validationResult = QuerySchema.safeParse(req.query);
    
    if (!validationResult.success) {
      return res.status(400).json({ 
        error: 'Invalid query parameters' 
      });
    }
    
    const { category, minPrice, maxPrice, page, limit } = validationResult.data;
    const offset = (page - 1) * limit;
    
    // Build query
    let query = supabase
      .from('products')
      .select('id, name, description, price, category, created_at', { count: 'exact' });
    
    // Apply filters
    if (category) {
      query = query.eq('category', category);
    }
    
    if (minPrice !== undefined) {
      query = query.gte('price', minPrice);
    }
    
    if (maxPrice !== undefined) {
      query = query.lte('price', maxPrice);
    }
    
    // Apply pagination
    query = query.range(offset, offset + limit - 1).order('created_at', { ascending: false });
    
    // Execute query
    const { data, error, count } = await query;
    
    if (error) {
      console.error('Database error:', error);
      return res.status(500).json({ error: 'Failed to fetch products' });
    }
    
    // Calculate pagination info
    const total = count || 0;
    const pages = Math.ceil(total / limit);
    
    return res.status(200).json({ 
      products: data || [], 
      pagination: {
        page,
        limit,
        total,
        pages
      }
    });
  } catch (error) {
    console.error('API error:', error);
    return res.status(500).json({ error: 'Internal server error' });
  }
}

This implementation:

  • Uses Zod for strong input validation
  • Implements rate limiting
  • Provides proper pagination
  • Returns only necessary data fields
  • Handles errors gracefully

Tempo Labs Secure Implementation

For Tempo Labs’ applications:

// Secure Tempo Labs API implementation
import { PrismaClient } from '@prisma/client';
import { verify, sign } from 'jsonwebtoken';
import { hash, compare } from 'bcrypt';
import { z } from 'zod';
import rateLimit from 'express-rate-limit';

const prisma = new PrismaClient();

// Rate limiting middleware
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many requests, please try again later' }
});

// Authentication middleware
async function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Authentication required' });
  }
  
  const token = authHeader.split(' ')[1];
  
  try {
    const decoded = verify(token, process.env.JWT_SECRET);
    
    // Check if user exists and is active
    const user = await prisma.user.findUnique({
      where: { id: decoded.userId },
      select: { id: true, role: true, active: true }
    });
    
    if (!user || !user.active) {
      return res.status(401).json({ error: 'Invalid or inactive user' });
    }
    
    req.user = {
      id: user.id,
      role: user.role
    };
    
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
}

// Authorization middleware
function authorize(roles) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }
    
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    
    next();
  };
}

// Input validation schemas
const UserSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  password: z.string().min(8).max(100)
});

const LoginSchema = z.object({
  email: z.string().email(),
  password: z.string()
});

// API route for user registration
export default async function handler(req, res) {
  // Apply rate limiting
  await apiLimiter(req, res, () => {});
  
  if (req.method === 'POST' && req.url === '/api/register') {
    try {
      // Validate input
      const validationResult = UserSchema.safeParse(req.body);
      
      if (!validationResult.success) {
        return res.status(400).json({ 
          error: 'Invalid input data',
          details: validationResult.error.format()
        });
      }
      
      const { name, email, password } = validationResult.data;
      
      // Check if user already exists
      const existingUser = await prisma.user.findUnique({
        where: { email }
      });
      
      if (existingUser) {
        return res.status(409).json({ error: 'Email already in use' });
      }
      
      // Hash password
      const hashedPassword = await hash(password, 12);
      
      // Create user
      const user = await prisma.user.create({
        data: {
          name,
          email,
          password: hashedPassword,
          role: 'user',
          active: true
        },
        select: {
          id: true,
          name: true,
          email: true,
          role: true,
          createdAt: true
        }
      });
      
      return res.status(201).json({
        message: 'User registered successfully',
        user
      });
    } catch (error) {
      console.error('Registration error:', error);
      return res.status(500).json({ error: 'Internal server error' });
    }
  } else if (req.method === 'POST' && req.url === '/api/login') {
    try {
      // Validate input
      const validationResult = LoginSchema.safeParse(req.body);
      
      if (!validationResult.success) {
        return res.status(400).json({ 
          error: 'Invalid input data' 
        });
      }
      
      const { email, password } = validationResult.data;
      
      // Find user
      const user = await prisma.user.findUnique({
        where: { email }
      });
      
      if (!user || !user.active) {
        return res.status(401).json({ error: 'Invalid credentials' });
      }
      
      // Verify password
      const passwordValid = await compare(password, user.password);
      
      if (!passwordValid) {
        return res.status(401).json({ error: 'Invalid credentials' });
      }
      
      // Generate token
      const token = sign(
        { userId: user.id, role: user.role },
        process.env.JWT_SECRET,
        { expiresIn: '1h' }
      );
      
      return res.status(200).json({
        token,
        user: {
          id: user.id,
          name: user.name,
          email: user.email,
          role: user.role
        }
      });
    } catch (error) {
      console.error('Login error:', error);
      return res.status(500).json({ error: 'Internal server error' });
    }
  } else if (req.method === 'GET' && req.url === '/api/users') {
    try {
      // Authenticate and authorize
      await authenticate(req, res, async () => {
        await authorize(['admin'])(req, res, async () => {
          // Pagination parameters
          const page = parseInt(req.query.page) || 1;
          const limit = Math.min(parseInt(req.query.limit) || 10, 100);
          const offset = (page - 1) * limit;
          
          // Fetch users with pagination
          const users = await prisma.user.findMany({
            select: {
              id: true,
              name: true,
              email: true,
              role: true,
              active: true,
              createdAt: true
            },
            skip: offset,
            take: limit,
            orderBy: { createdAt: 'desc' }
          });
          
          // Get total count
          const total = await prisma.user.count();
          
          return res.status(200).json({
            users,
            pagination: {
              page,
              limit,
              total,
              pages: Math.ceil(total / limit)
            }
          });
        });
      });
    } catch (error) {
      console.error('Users fetch error:', error);
      return res.status(500).json({ error: 'Internal server error' });
    }
  } else {
    return res.status(405).json({ error: 'Method not allowed' });
  }
}

This implementation:

  • Uses Prisma ORM for database operations
  • Implements proper authentication and authorization
  • Uses bcrypt for secure password hashing
  • Validates input with Zod
  • Implements rate limiting
  • Provides pagination for data fetching

Base44 Secure Implementation

For Base44’s applications:

// Secure Base44 API implementation
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const { body, param, validationResult } = require('express-validator');
const rateLimit = require('express-rate-limit');
const mongoose = require('mongoose');
const User = require('../models/User');

// Rate limiting middleware
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many requests, please try again later' }
});

const authLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 10, // Limit each IP to 10 requests per windowMs
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many login attempts, please try again later' }
});

// Apply rate limiting
router.use(apiLimiter);
router.use('/login', authLimiter);
router.use('/register', authLimiter);

// Authentication middleware
function authenticate(req, res, next) {
  const token = req.header('Authorization')?.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'Authentication required' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
}

// Authorization middleware
function authorize(roles) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }
    
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    
    next();
  };
}

// Register new user
router.post(
  '/register',
  [
    body('username')
      .trim()
      .isLength({ min: 3, max: 30 })
      .withMessage('Username must be between 3 and 30 characters'),
    
    body('email')
      .isEmail()
      .withMessage('Must be a valid email address')
      .normalizeEmail(),
    
    body('password')
      .isLength({ min: 8 })
      .withMessage('Password must be at least 8 characters long')
      .matches(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*])/)
      .withMessage('Password must include uppercase, lowercase, number, and special character')
  ],
  async (req, res) => {
    // Check for validation errors
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    
    const { username, email, password } = req.body;
    
    try {
      // Check if user already exists
      const existingUser = await User.findOne({ 
        $or: [{ email }, { username }] 
      });
      
      if (existingUser) {
        return res.status(409).json({ 
          error: 'User already exists with that email or username' 
        });
      }
      
      // Hash password
      const salt = await bcrypt.genSalt(12);
      const hashedPassword = await bcrypt.hash(password, salt);
      
      // Create new user
      const user = new User({
        username,
        email,
        password: hashedPassword,
        role: 'user'
      });
      
      await user.save();
      
      res.status(201).json({ 
        message: 'User registered successfully',
        userId: user._id
      });
    } catch (error) {
      console.error('Registration error:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

// Login
router.post(
  '/login',
  [
    body('email')
      .isEmail()
      .withMessage('Must be a valid email address')
      .normalizeEmail(),
    
    body('password')
      .notEmpty()
      .withMessage('Password is required')
  ],
  async (req, res) => {
    // Check for validation errors
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    
    const { email, password } = req.body;
    
    try {
      // Find user
      const user = await User.findOne({ email });
      
      if (!user) {
        return res.status(401).json({ error: 'Invalid credentials' });
      }
      
      // Check password
      const isMatch = await bcrypt.compare(password, user.password);
      
      if (!isMatch) {
        return res.status(401).json({ error: 'Invalid credentials' });
      }
      
      // Generate token
      const token = jwt.sign(
        { 
          userId: user._id,
          role: user.role
        },
        process.env.JWT_SECRET,
        { expiresIn: '1h' }
      );
      
      res.json({ 
        token,
        user: {
          id: user._id,
          username: user.username,
          email: user.email,
          role: user.role
        }
      });
    } catch (error) {
      console.error('Login error:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

// Get user profile
router.get(
  '/users/profile',
  authenticate,
  async (req, res) => {
    try {
      const user = await User.findById(req.user.userId)
        .select('-password');
      
      if (!user) {
        return res.status(404).json({ error: 'User not found' });
      }
      
      res.json(user);
    } catch (error) {
      console.error('Profile fetch error:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

// Get all users (admin only)
router.get(
  '/users',
  authenticate,
  authorize(['admin']),
  async (req, res) => {
    try {
      // Pagination
      const page = parseInt(req.query.page) || 1;
      const limit = Math.min(parseInt(req.query.limit) || 10, 100);
      const skip = (page - 1) * limit;
      
      // Get users with pagination
      const users = await User.find()
        .select('-password')
        .skip(skip)
        .limit(limit)
        .sort({ createdAt: -1 });
      
      // Get total count
      const total = await User.countDocuments();
      
      res.json({
        users,
        pagination: {
          page,
          limit,
          total,
          pages: Math.ceil(total / limit)
        }
      });
    } catch (error) {
      console.error('Users fetch error:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

// Update user
router.put(
  '/users/:id',
  authenticate,
  [
    param('id')
      .custom(value => mongoose.Types.ObjectId.isValid(value))
      .withMessage('Invalid user ID'),
    
    body('username')
      .optional()
      .trim()
      .isLength({ min: 3, max: 30 })
      .withMessage('Username must be between 3 and 30 characters'),
    
    body('email')
      .optional()
      .isEmail()
      .withMessage('Must be a valid email address')
      .normalizeEmail(),
    
    body('password')
      .optional()
      .isLength({ min: 8 })
      .withMessage('Password must be at least 8 characters long')
      .matches(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*])/)
      .withMessage('Password must include uppercase, lowercase, number, and special character')
  ],
  async (req, res) => {
    // Check for validation errors
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    
    const userId = req.params.id;
    
    // Check if user is updating their own profile or is an admin
    if (req.user.userId !== userId && req.user.role !== 'admin') {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    
    try {
      const user = await User.findById(userId);
      
      if (!user) {
        return res.status(404).json({ error: 'User not found' });
      }
      
      // Prepare update data
      const updateData = {};
      
      if (req.body.username) updateData.username = req.body.username;
      if (req.body.email) updateData.email = req.body.email;
      
      if (req.body.password) {
        const salt = await bcrypt.genSalt(12);
        updateData.password = await bcrypt.hash(req.body.password, salt);
      }
      
      // Only admins can update role
      if (req.body.role && req.user.role === 'admin') {
        updateData.role = req.body.role;
      }
      
      // Update user
      const updatedUser = await User.findByIdAndUpdate(
        userId,
        updateData,
        { new: true }
      ).select('-password');
      
      res.json({
        message: 'User updated successfully',
        user: updatedUser
      });
    } catch (error) {
      console.error('Update error:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

// Delete user
router.delete(
  '/users/:id',
  authenticate,
  [
    param('id')
      .custom(value => mongoose.Types.ObjectId.isValid(value))
      .withMessage('Invalid user ID')
  ],
  async (req, res) => {
    // Check for validation errors
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    
    const userId = req.params.id;
    
    // Check if user is deleting their own account or is an admin
    if (req.user.userId !== userId && req.user.role !== 'admin') {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    
    try {
      const user = await User.findById(userId);
      
      if (!user) {
        return res.status(404).json({ error: 'User not found' });
      }
      
      await User.findByIdAndDelete(userId);
      
      res.json({ message: 'User deleted successfully' });
    } catch (error) {
      console.error('Delete error:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

module.exports = router;

This implementation:

  • Uses bcrypt for secure password hashing
  • Implements JWT-based authentication
  • Uses express-validator for input validation
  • Implements role-based authorization
  • Provides pagination for data fetching
  • Implements rate limiting
  • Handles errors gracefully

Replit Secure Implementation

For Replit’s Python Flask applications:

# Secure Replit API implementation
from flask import Flask, request, jsonify, g
from flask_cors import CORS
from werkzeug.security import generate_password_hash, check_password_hash
import sqlite3
import jwt
import datetime
import os
import re
from functools import wraps
import time
from threading import Lock

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('JWT_SECRET_KEY', 'default-dev-key-change-in-production')

# Configure CORS
CORS(app, resources={
    r"/api/*": {
        "origins": [
            "https://yourapplication.com",
            "https://admin.yourapplication.com"
        ],
        "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
        "allow_headers": ["Content-Type", "Authorization"]
    }
})

# Database setup
DATABASE = 'database.db'

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = sqlite3.connect(DATABASE)
        db.row_factory = sqlite3.Row
    return db

@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

# Initialize database
def init_db():
    with app.app_context():
        db = get_db()
        with app.open_resource('schema.sql', mode='r') as f:
            db.cursor().executescript(f.read())
        db.commit()

# Rate limiting
class RateLimiter:
    def __init__(self):
        self.ip_cache = {}
        self.lock = Lock()
    
    def is_rate_limited(self, ip, limit, window):
        with self.lock:
            now = time.time()
            
            if ip not in self.ip_cache:
                self.ip_cache[ip] = {'count': 1, 'window_start': now}
                return False
            
            cache_data = self.ip_cache[ip]
            
            # Reset if window has passed
            if now - cache_data['window_start'] > window:
                cache_data['count'] = 1
                cache_data['window_start'] = now
                return False
            
            # Check if over limit
            if cache_data['count'] >= limit:
                return True
            
            # Increment count
            cache_data['count'] += 1
            return False

rate_limiter = RateLimiter()

# Authentication decorator
def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = None
        
        # Check for rate limiting
        ip = request.remote_addr
        if rate_limiter.is_rate_limited(ip, 100, 60 * 15):  # 100 requests per 15 minutes
            return jsonify({'error': 'Rate limit exceeded'}), 429
        
        # Get token from header
        auth_header = request.headers.get('Authorization')
        if auth_header and auth_header.startswith('Bearer '):
            token = auth_header.split(' ')[1]
        
        if not token:
            return jsonify({'error': 'Authentication required'}), 401
        
        try:
            # Decode token
            data = jwt.decode(
                token, 
                app.config['SECRET_KEY'],
                algorithms=['HS256']
            )
            
            # Get user from database
            db = get_db()
            user = db.execute(
                'SELECT id, username, email, role, active FROM users WHERE id = ?',
                (data['user_id'],)
            ).fetchone()
            
            if not user or not user['active']:
                return jsonify({'error': 'Invalid or inactive user'}), 401
            
            # Store user in request context
            g.user = {
                'id': user['id'],
                'username': user['username'],
                'email': user['email'],
                'role': user['role']
            }
        except jwt.ExpiredSignatureError:
            return jsonify({'error': 'Token has expired'}), 401
        except jwt.InvalidTokenError:
            return jsonify({'error': 'Invalid token'}), 401
        
        return f(*args, **kwargs)
    
    return decorated

# Role-based authorization decorator
def role_required(roles):
    def decorator(f):
        @wraps(f)
        def decorated(*args, **kwargs):
            if not hasattr(g, 'user'):
                return jsonify({'error': 'Authentication required'}), 401
            
            if g.user['role'] not in roles:
                return jsonify({'error': 'Insufficient permissions'}), 403
            
            return f(*args, **kwargs)
        return decorated
    return decorator

# Input validation functions
def validate_username(username):
    if not username or not isinstance(username, str):
        return False
    if len(username) < 3 or len(username) > 30:
        return False
    if not re.match(r'^[a-zA-Z0-9_]+$', username):
        return False
    return True

def validate_email(email):
    if not email or not isinstance(email, str):
        return False
    if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
        return False
    return True

def validate_password(password):
    if not password or not isinstance(password, str):
        return False
    if len(password) < 8:
        return False
    if not re.search(r'[A-Z]', password):
        return False
    if not re.search(r'[a-z]', password):
        return False
    if not re.search(r'[0-9]', password):
        return False
    if not re.search(r'[!@#$%^&*]', password):
        return False
    return True

# Register endpoint
@app.route('/api/register', methods=['POST'])
def register():
    # Check for rate limiting
    ip = request.remote_addr
    if rate_limiter.is_rate_limited(ip, 10, 60 * 60):  # 10 requests per hour
        return jsonify({'error': 'Rate limit exceeded'}), 429
    
    data = request.get_json()
    
    # Validate input
    if not data:
        return jsonify({'error': 'No input data provided'}), 400
    
    username = data.get('username')
    email = data.get('email')
    password = data.get('password')
    
    if not validate_username(username):
        return jsonify({'error': 'Invalid username format'}), 400
    
    if not validate_email(email):
        return jsonify({'error': 'Invalid email format'}), 400
    
    if not validate_password(password):
        return jsonify({'error': 'Password does not meet requirements'}), 400
    
    db = get_db()
    
    # Check if user already exists
    existing_user = db.execute(
        'SELECT id FROM users WHERE email = ? OR username = ?',
        (email, username)
    ).fetchone()
    
    if existing_user:
        return jsonify({'error': 'User already exists with that email or username'}), 409
    
    # Hash password
    hashed_password = generate_password_hash(password, method='pbkdf2:sha256', salt_length=16)
    
    # Create user
    db.execute(
        'INSERT INTO users (username, email, password, role, active) VALUES (?, ?, ?, ?, ?)',
        (username, email, hashed_password, 'user', 1)
    )
    db.commit()
    
    return jsonify({'message': 'User registered successfully'}), 201

# Login endpoint
@app.route('/api/login', methods=['POST'])
def login():
    # Check for rate limiting
    ip = request.remote_addr
    if rate_limiter.is_rate_limited(ip, 10, 60 * 60):  # 10 requests per hour
        return jsonify({'error': 'Rate limit exceeded'}), 429
    
    data = request.get_json()
    
    if not data:
        return jsonify({'error': 'No input data provided'}), 400
    
    email = data.get('email')
    password = data.get('password')
    
    if not email or not password:
        return jsonify({'error': 'Email and password are required'}), 400
    
    db = get_db()
    
    # Find user
    user = db.execute(
        'SELECT id, username, email, password, role, active FROM users WHERE email = ?',
        (email,)
    ).fetchone()
    
    if not user or not user['active']:
        return jsonify({'error': 'Invalid credentials'}), 401
    
    # Check password
    if not check_password_hash(user['password'], password):
        return jsonify({'error': 'Invalid credentials'}), 401
    
    # Generate token
    token = jwt.encode(
        {
            'user_id': user['id'],
            'role': user['role'],
            'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
        },
        app.config['SECRET_KEY'],
        algorithm='HS256'
    )
    
    return jsonify({
        'token': token,
        'user': {
            'id': user['id'],
            'username': user['username'],
            'email': user['email'],
            'role': user['role']
        }
    }), 200

# Get user profile
@app.route('/api/users/profile', methods=['GET'])
@token_required
def get_profile():
    return jsonify(g.user), 200

# Get all users (admin only)
@app.route('/api/users', methods=['GET'])
@token_required
@role_required(['admin'])
def get_users():
    # Pagination
    page = request.args.get('page', 1, type=int)
    limit = min(request.args.get('limit', 10, type=int), 100)
    offset = (page - 1) * limit
    
    db = get_db()
    
    # Get users with pagination
    users = db.execute(
        'SELECT id, username, email, role, active, created_at FROM users LIMIT ? OFFSET ?',
        (limit, offset)
    ).fetchall()
    
    # Get total count
    total = db.execute('SELECT COUNT(*) as count FROM users').fetchone()['count']
    
    return jsonify({
        'users': [dict(user) for user in users],
        'pagination': {
            'page': page,
            'limit': limit,
            'total': total,
            'pages': (total + limit - 1) // limit
        }
    }), 200

# Update user
@app.route('/api/users/<int:user_id>', methods=['PUT'])
@token_required
def update_user(user_id):
    # Check if user is updating their own profile or is an admin
    if g.user['id'] != user_id and g.user['role'] != 'admin':
        return jsonify({'error': 'Insufficient permissions'}), 403
    
    data = request.get_json()
    
    if not data:
        return jsonify({'error': 'No input data provided'}), 400
    
    db = get_db()
    
    # Check if user exists
    user = db.execute(
        'SELECT id FROM users WHERE id = ?',
        (user_id,)
    ).fetchone()
    
    if not user:
        return jsonify({'error': 'User not found'}), 404
    
    # Prepare update data
    updates = []
    params = []
    
    if 'username' in data:
        if not validate_username(data['username']):
            return jsonify({'error': 'Invalid username format'}), 400
        updates.append('username = ?')
        params.append(data['username'])
    
    if 'email' in data:
        if not validate_email(data['email']):
            return jsonify({'error': 'Invalid email format'}), 400
        updates.append('email = ?')
        params.append(data['email'])
    
    if 'password' in data:
        if not validate_password(data['password']):
            return jsonify({'error': 'Password does not meet requirements'}), 400
        hashed_password = generate_password_hash(data['password'], method='pbkdf2:sha256', salt_length=16)
        updates.append('password = ?')
        params.append(hashed_password)
    
    # Only admins can update role
    if 'role' in data and g.user['role'] == 'admin':
        if data['role'] not in ['user', 'admin']:
            return jsonify({'error': 'Invalid role'}), 400
        updates.append('role = ?')
        params.append(data['role'])
    
    if not updates:
        return jsonify({'error': 'No valid fields to update'}), 400
    
    # Add user_id to params
    params.append(user_id)
    
    # Update user
    db.execute(
        f"UPDATE users SET {', '.join(updates)} WHERE id = ?",
        params
    )
    db.commit()
    
    # Get updated user
    updated_user = db.execute(
        'SELECT id, username, email, role, active, created_at FROM users WHERE id = ?',
        (user_id,)
    ).fetchone()
    
    return jsonify({
        'message': 'User updated successfully',
        'user': dict(updated_user)
    }), 200

# Delete user
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
@token_required
def delete_user(user_id):
    # Check if user is deleting their own account or is an admin
    if g.user['id'] != user_id and g.user['role'] != 'admin':
        return jsonify({'error': 'Insufficient permissions'}), 403
    
    db = get_db()
    
    # Check if user exists
    user = db.execute(
        'SELECT id FROM users WHERE id = ?',
        (user_id,)
    ).fetchone()
    
    if not user:
        return jsonify({'error': 'User not found'}), 404
    
    # Delete user
    db.execute('DELETE FROM users WHERE id = ?', (user_id,))
    db.commit()
    
    return jsonify({'message': 'User deleted successfully'}), 200

if __name__ == '__main__':
    # In production, use a proper WSGI server and set debug=False
    app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 8080)), debug=False)

This implementation:

  • Uses werkzeug for secure password hashing
  • Implements JWT-based authentication
  • Uses custom input validation
  • Implements role-based authorization
  • Provides pagination for data fetching
  • Implements rate limiting
  • Configures CORS properly
  • Handles errors gracefully

Best Practices for API Security

To ensure your vibe-coded APIs remain secure, follow these best practices:

1. Use Specific Prompts for Security

When using AI tools to generate API code, include security requirements in your prompts:

Instead of:

Create an API endpoint to get user data

Use:

Create a secure API endpoint to get user data with proper authentication, authorization, input validation, rate limiting, and error handling. Ensure sensitive data is protected and only authorized users can access it.

2. Implement Defense in Depth

Don’t rely on a single security measure:

  • Combine authentication, authorization, input validation, and rate limiting
  • Use HTTPS for all API traffic
  • Implement proper CORS configuration
  • Use security headers (Content-Security-Policy, X-Content-Type-Options, etc.)
  • Apply the principle of least privilege

3. Secure Authentication and Authorization

Implement robust authentication and authorization:

  • Use industry-standard authentication mechanisms (JWT, OAuth 2.0)
  • Implement role-based access control
  • Enforce the principle of least privilege
  • Use secure password storage with proper hashing
  • Implement token expiration and refresh mechanisms
  • Consider multi-factor authentication for sensitive operations

4. Validate and Sanitize All Input

Never trust client input:

  • Validate all input data (type, format, range, etc.)
  • Use schema validation libraries (Zod, Joi, express-validator)
  • Implement whitelist validation instead of blacklist
  • Sanitize data before using it in database queries or responses
  • Validate query parameters, URL parameters, headers, and request body

5. Implement Rate Limiting

Protect against abuse:

  • Limit request frequency based on IP, user, or API key
  • Apply stricter limits to authentication endpoints
  • Use token bucket or sliding window algorithms
  • Provide clear feedback when limits are exceeded
  • Consider dynamic rate limiting based on user roles

6. Use Secure Error Handling

Prevent information leakage:

  • Return generic error messages to clients
  • Log detailed errors for internal debugging
  • Use appropriate HTTP status codes
  • Don’t expose stack traces, database errors, or system information
  • Implement global error handling middleware

7. Secure Data Transmission

Protect data in transit:

  • Enforce HTTPS for all API traffic
  • Implement HTTP Strict Transport Security (HSTS)
  • Use secure cookies with HttpOnly, Secure, and SameSite flags
  • Consider field-level encryption for highly sensitive data
  • Implement proper CORS configuration

8. Regular Security Testing

Continuously test for API vulnerabilities:

  • Implement automated security testing in CI/CD pipeline
  • Use tools like OWASP ZAP, Burp Suite, or Postman for API testing
  • Perform regular security audits
  • Consider penetration testing for critical APIs
  • Monitor for unusual patterns or potential attacks

Conclusion

API security is a critical aspect of application development, especially in the context of vibe coding where AI tools might prioritize functionality over security. By understanding common API security vulnerabilities in AI-generated code and implementing secure alternatives, you can protect your applications from unauthorized access, data breaches, and service disruptions.

The key principles for securing APIs in vibe-coded applications include:

  1. Implementing proper authentication and authorization to control access to your API
  2. Validating and sanitizing all input to prevent injection attacks and data corruption
  3. Implementing rate limiting to protect against abuse and denial of service
  4. Using secure error handling to prevent information leakage
  5. Enforcing HTTPS to protect data in transit
  6. Configuring CORS properly to control cross-origin access
  7. Following the principle of least privilege to minimize potential damage from compromised accounts
  8. Regularly testing for security vulnerabilities to identify and address issues early

Each full-stack app builder—Lovable.dev, Bolt.new, Tempo Labs, Base44, and Replit—has its own API implementation patterns and potential vulnerabilities. By applying platform-specific security enhancements and following best practices, you can ensure that your vibe-coded applications maintain robust protection against API-related threats while still benefiting from the rapid development that AI tools enable.

Remember that a single API vulnerability can compromise your entire application. Investing time in securing your APIs will protect your users’ data and your reputation from the devastating consequences of a successful attack.

Additional Resources

References

  1. Salt Security. (2023). “State of API Security Report.” Retrieved from https://salt.security/api-security-trends
  2. OWASP. (2023). “API Security Top 10.” Retrieved from https://owasp.org/API-Security/editions/2023/en/0x00-introduction/
  3. Ping Identity. (2022). “API Security: The Complete Guide.” Retrieved from https://www.pingidentity.com/en/resources/blog/post/complete-guide-api-security.html
  4. Postman. (2023). “2023 State of the API Report.” Retrieved from https://www.postman.com/state-of-api/
  5. Gartner. (2022). “API Security: Protecting Your Application Programming Interfaces.” Retrieved from https://www.gartner.com/en/documents/4016095