LogoBoring Template

Security

Overview of security measures and best practices implemented across the application.

This document provides an overview of security measures and best practices implemented across the application to protect user data and prevent abuse.

Rate Limiting

Protection

Rate limiting prevents abuse, brute force attacks, and potential DDoS attempts

Fair Usage

Ensures resources are distributed fairly among all users

Cost Control

Prevents excessive resource consumption in pay-per-use services

Implementation

Rate limiting is implemented using Upstash Redis to prevent abuse and DDoS attacks. The implementation uses a sliding window algorithm to track request counts over time.

lib/rate-limit.ts
import { Ratelimit } from "@upstash/ratelimit";
import { redis } from "@/lib/upstash";
 
// Create a new ratelimiter with 5 requests per minute
export const ratelimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(5, "1 m"), // 5 requests per minute
});

Server Action Example

Example of rate limiting in server actions:

app/actions/protected-action.ts
import { ratelimit } from "@/lib/rate-limit";
import { RateLimitError } from "@/lib/errors";
 
export async function protectedAction(input: any) {
  const { user } = await getCurrentUser();
 
  // Create a unique identifier for this user and action
  const identifier = `ratelimit:action:${user.id}`;
 
  // Check if the request is within rate limits
  const { success } = await ratelimit.limit(identifier);
 
  if (!success) {
    throw new RateLimitError();
  }
 
  // Action implementation...
}

API Route Example

Implementation in API routes:

app/api/protected/route.ts
import { ratelimit } from "@/lib/rate-limit";
import { RateLimitError } from "@/lib/errors";
import { NextRequest, NextResponse } from "next/server";
 
export async function POST(req: NextRequest) {
  const ip = req.ip || "127.0.0.1";
 
  // Create a unique identifier based on IP address
  const identifier = `ratelimit:api:${ip}`;
 
  // Check if the request is within rate limits
  const { success } = await ratelimit.limit(identifier);
 
  if (!success) {
    return NextResponse.json({ error: "Too many requests" }, { status: 429 });
  }
 
  // Route implementation...
}

Rate limits are applied per user and per action to ensure fair usage and prevent abuse. Different actions can have different rate limits based on their resource requirements.

Server Actions Security

Server actions require multiple security layers to ensure they're used safely and correctly.

Authentication Check

Every server action verifies user authentication before proceeding:

export async function protectedAction() {
  // Get current user and verify authentication
  const { user } = await getCurrentUser();
 
  if (!user) {
    throw new AuthenticationError();
  }
 
  // Continue with authenticated action...
}

Input Validation

All inputs are validated using Zod schemas to prevent injection attacks and ensure data integrity:

import { z } from "zod";
import { ValidationError } from "@/lib/errors";
 
// Define schema for input validation
const inputSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  // Other fields...
});
 
export async function userAction(input: unknown) {
  // Validate input against schema
  const validateInput = inputSchema.safeParse(input);
 
  if (!validateInput.success) {
    throw new ValidationError(validateInput.error.message);
  }
 
  // Continue with validated input...
  const { name, email } = validateInput.data;
}

Permission Check

Actions verify user permissions before executing sensitive operations:

export async function deleteResource(resourceId: string) {
  const { user } = await getCurrentUser();
 
  if (!user) {
    throw new AuthenticationError();
  }
 
  // Check if user has permission for this action
  const hasPermission = await checkPermission(user.id, "resource:delete");
 
  if (!hasPermission) {
    throw new AuthorizationError();
  }
 
  // Continue with authorized action...
}

API Endpoints

API endpoints require robust security measures to protect against common attacks.

Authentication

JWT Token Validation

Verify JWT tokens with proper signature and expiration checks

Session Verification

Validate active user sessions for each request

Role-based Access Control

Restrict endpoints based on user roles and permissions

Secure Cookie Handling

Use HTTP-only, secure cookies with proper expiration

Protection Mechanisms

middleware.ts
import { NextResponse, type NextRequest } from "next/server";
import { verifyAuth } from "@/lib/auth";
 
export async function middleware(request: NextRequest) {
  // Get token from cookies
  const token = request.cookies.get("token")?.value;
 
  // Verify authentication
  const verifiedToken = token && (await verifyAuth(token).catch(() => null));
 
  // Check if this is an API route that requires authentication
  if (request.nextUrl.pathname.startsWith("/api/") && !verifiedToken) {
    return NextResponse.json(
      { error: "Authentication required" },
      { status: 401 }
    );
  }
 
  return NextResponse.next();
}
 
export const config = {
  matcher: [
    // Apply this middleware to all API routes
    "/api/:path*",
  ],
};

CORS Configuration

Properly configured CORS settings prevent unauthorized cross-origin requests:

app/api/public/route.ts
import { NextResponse, type NextRequest } from "next/server";
 
export async function OPTIONS(request: NextRequest) {
  // This is needed for preflight requests
  return new NextResponse(null, {
    status: 204,
    headers: {
      "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
      "Access-Control-Allow-Origin": "https://trusted-domain.com",
      "Access-Control-Max-Age": "86400",
    },
  });
}
 
export async function GET(request: NextRequest) {
  // Handle the actual request
  const response = NextResponse.json({ data: "Public API data" });
 
  // Set CORS headers on the response
  response.headers.set(
    "Access-Control-Allow-Origin",
    "https://trusted-domain.com"
  );
 
  return response;
}

Security Best Practices

Data Protection

  1. Encrypt Sensitive Data: Always encrypt sensitive data at rest and in transit
  2. Use HTTPS Only: Enforce HTTPS for all communications
  3. Implement Proper CORS: Restrict cross-origin requests to trusted domains
  4. Secure Cookie Settings: Use HTTP-only, secure, SameSite cookies

Authentication

  1. Strong Password Policies: Enforce minimum complexity requirements
  2. MFA When Available: Implement multi-factor authentication
  3. Session Management: Set appropriate timeouts and validation
  4. Regular Token Rotation: Refresh tokens regularly to limit exposure

Infrastructure Security

  1. Dependency Updates: Regularly update dependencies to patch vulnerabilities
  2. Security Headers: Implement proper security headers (CSP, HSTS, etc.)
  3. Regular Audits: Conduct security audits and penetration testing
  4. Least Privilege: Follow principle of least privilege for all systems

Error Handling

Proper error handling is crucial for security to avoid leaking sensitive information.

Custom Error Types

Standardized error handling for security-related issues:

lib/errors.ts
// Base API error class
export class ApiError extends Error {
  statusCode: number;
 
  constructor(statusCode: number, message: string) {
    super(message);
    this.statusCode = statusCode;
    this.name = this.constructor.name;
  }
}
 
// Authentication errors
export class AuthenticationError extends ApiError {
  constructor(message = "Not authenticated") {
    super(401, message);
  }
}
 
// Authorization errors
export class AuthorizationError extends ApiError {
  constructor(message = "Not authorized") {
    super(403, message);
  }
}
 
// Rate limiting errors
export class RateLimitError extends ApiError {
  constructor(message = "Too many requests") {
    super(429, message);
  }
}
 
// Validation errors
export class ValidationError extends ApiError {
  constructor(message = "Invalid input") {
    super(400, message);
  }
}

Error Handler

Centralized error handling for consistent security responses:

lib/error-handler.ts
import { ApiError } from "@/lib/errors";
import { NextResponse } from "next/server";
 
export function handleError(error: unknown) {
  console.error("Error:", error);
 
  if (error instanceof ApiError) {
    // Return appropriate status code and message for known errors
    return NextResponse.json(
      { error: error.message },
      { status: error.statusCode }
    );
  }
 
  // For unknown errors, return a generic message
  return NextResponse.json(
    { error: "An unexpected error occurred" },
    { status: 500 }
  );
}

Security Monitoring

Implement proper logging and monitoring to detect and respond to security incidents quickly.

Logging Sensitive Actions

import { logger } from "@/lib/logger";
 
export async function sensitiveAction(userId: string, resourceId: string) {
  // Log the action attempt
  logger.info("Sensitive action attempted", {
    userId,
    resourceId,
    action: "sensitiveAction",
  });
 
  try {
    // Perform the action...
 
    // Log success
    logger.info("Sensitive action completed", {
      userId,
      resourceId,
      action: "sensitiveAction",
      status: "success",
    });
  } catch (error) {
    // Log failure with error details
    logger.error("Sensitive action failed", {
      userId,
      resourceId,
      action: "sensitiveAction",
      status: "error",
      error: error instanceof Error ? error.message : String(error),
    });
 
    throw error;
  }
}

Audit Trail

Maintain an audit trail for security-relevant actions:

lib/audit.ts
import { db } from "@/lib/db";
import { auditLogs } from "@/db/schema";
 
export async function recordAuditLog(data: {
  userId: string;
  action: string;
  resourceType: string;
  resourceId: string;
  details?: Record<string, any>;
}) {
  // Record audit log entry in database
  await db.insert(auditLogs).values({
    userId: data.userId,
    action: data.action,
    resourceType: data.resourceType,
    resourceId: data.resourceId,
    details: data.details ? JSON.stringify(data.details) : null,
    createdAt: new Date(),
  });
}

Additional Resources