Guides/Security

Authentication & Authorization

Secure your file uploads with proper authentication and authorization patterns

Authentication & Authorization

Secure your file upload endpoints with robust authentication and authorization middleware.

Important: Never expose upload endpoints without proper authentication in production. Unprotected endpoints can lead to storage abuse and security vulnerabilities.

Authentication Patterns

NextAuth.js Integration

import { s3 } from "@/lib/upload";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";

const s3Router = s3.createRouter({
  userFiles: s3.image()
    .max("5MB")
    .count(10)
    .middleware(async ({ req, metadata }) => {
      const session = await getServerSession(authOptions);

      if (!session?.user?.id) {
        throw new Error("Authentication required");
      }

      return {
        ...metadata,
        userId: session.user.id,
        userEmail: session.user.email,
      };
    }),
});

export const { GET, POST } = s3Router.handlers;

JWT Token Validation

import jwt from "jsonwebtoken";

const s3Router = s3.createRouter({
  protectedUploads: s3.file()
    .max("10MB")
    .count(5)
    .middleware(async ({ req, metadata }) => {
      const token = req.headers.get("authorization")?.replace("Bearer ", "");

      if (!token) {
        throw new Error("Authorization token required");
      }

      try {
        const payload = jwt.verify(token, process.env.JWT_SECRET!) as any;

        return {
          ...metadata,
          userId: payload.sub,
          roles: payload.roles || [],
        };
      } catch (error) {
        throw new Error("Invalid or expired token");
      }
    }),
});

Custom Authentication

const s3Router = s3.createRouter({
  apiKeyUploads: s3.file()
    .max("25MB")
    .count(1)
    .types(['application/pdf', 'application/msword'])
    .middleware(async ({ req, metadata }) => {
      const apiKey = req.headers.get("x-api-key");

      if (!apiKey) {
        throw new Error("API key required");
      }

      // Validate API key against your database
      const client = await validateApiKey(apiKey);

      if (!client) {
        throw new Error("Invalid API key");
      }

      return {
        ...metadata,
        clientId: client.id,
        plan: client.plan,
        quotaUsed: client.quotaUsed,
      };
    }),
});

Authorization Strategies

Role-Based Access Control (RBAC)

PropTypeDefault
guest?
string
-
user?
string
-
moderator?
string
-
admin?
string
-
const s3Router = s3.createRouter({
  adminUploads: s3.file()
    .max("100MB")
    .count(50)
    .middleware(async ({ req, metadata }) => {
      const { userId, roles } = await authenticateUser(req);

      if (!roles.includes("admin")) {
        throw new Error("Admin access required");
      }

      return { ...metadata, userId, roles };
    }),

  moderatorUploads: s3.image()
    .max("10MB")
    .count(20)
    .middleware(async ({ req, metadata }) => {
      const { userId, roles } = await authenticateUser(req);

      if (!roles.includes("admin") && !roles.includes("moderator")) {
        throw new Error("Moderator access required");
      }

      return { ...metadata, userId, roles };
    }),

  userUploads: s3.image()
    .max("5MB")
    .count(5)
    .middleware(async ({ req, metadata }) => {
      const { userId } = await authenticateUser(req);

      // Basic authentication only
      return { ...metadata, userId };
    }),
});

Resource-Based Authorization

const s3Router = s3.createRouter({
  projectFiles: s3.file()
    .max("25MB")
    .count(10)
    .middleware(async ({ req, metadata }) => {
      const { userId } = await authenticateUser(req);
      const projectId = req.url.searchParams.get("projectId");

      if (!projectId) {
        throw new Error("Project ID required");
      }

      // Check if user has access to this project
      const hasAccess = await checkProjectAccess(userId, projectId);

      if (!hasAccess) {
        throw new Error("Access denied to this project");
      }

      return { ...metadata, userId, projectId };
    }),
});

Attribute-Based Access Control (ABAC)

interface AccessContext {
  userId: string;
  userRole: string;
  resourceType: string;
  action: string;
  environment: string;
}

async function checkAccess(context: AccessContext): Promise<boolean> {
  // Complex policy evaluation
  const policies = await getPolicies(context.userId);

  return policies.some(
    (policy) =>
      policy.resource === context.resourceType &&
      policy.actions.includes(context.action) &&
      policy.environment.includes(context.environment)
  );
}

export const router = createUploadRouter({
  sensitiveFiles: uploadSchema({
    document: { maxSize: "10MB", maxCount: 1 },
  }).middleware(async ({ req }) => {
    const { userId, role } = await authenticateUser(req);

    const hasAccess = await checkAccess({
      userId,
      userRole: role,
      resourceType: "sensitive-document",
      action: "upload",
      environment: process.env.NODE_ENV || "development",
    });

    if (!hasAccess) {
      throw new Error("Access denied by policy");
    }

    return { userId, role };
  }),
});

Security Best Practices

Input Validation

Always validate file content and metadata

.middleware(async ({ req, files }) => {
  for (const file of files) {
    // Validate file headers
    if (!await isValidFileType(file)) {
      throw new Error("Invalid file type")
    }
    
    // Check for malicious content
    if (await containsMalware(file)) {
      throw new Error("File contains malicious content")
    }
  }
  
  return { userId: await getUserId(req) }
})

Rate Limiting

Prevent abuse with upload rate limits

import { ratelimit } from '@/lib/ratelimit'

.middleware(async ({ req }) => {
  const identifier = await getUserId(req) || getClientIP(req)
  
  const { success } = await ratelimit.limit(identifier)
  if (!success) {
    throw new Error("Rate limit exceeded")
  }
  
  return { userId: identifier }
})

Quota Management

Enforce storage and upload quotas

.middleware(async ({ req, files }) => {
  const userId = await getUserId(req)
  const totalSize = files.reduce((sum, f) => sum + f.size, 0)
  
  const quota = await getUserQuota(userId)
  if (quota.used + totalSize > quota.limit) {
    throw new Error("Storage quota exceeded")
  }
  
  return { userId }
})

Client-Side Security

Secure Token Handling

// lib/upload-client.ts
export const upload = createUploadClient<AppRouter>({
  endpoint: "/api/upload",
  headers: {
    // Use secure token storage
    Authorization: `Bearer ${getSecureToken()}`,
  },
  onError: (error) => {
    if (error.status === 401) {
      // Handle token expiration
      refreshToken().then(() => {
        // Retry the upload with new token
        window.location.reload();
      });
    }
  },
});

// Secure token storage
function getSecureToken(): string {
  // Use httpOnly cookies or secure storage
  return (
    document.cookie
      .split("; ")
      .find((row) => row.startsWith("auth-token="))
      ?.split("=")[1] || ""
  );
}

CSRF Protection

// Server-side CSRF validation
import { csrf } from "@/lib/csrf";

export const router = createUploadRouter({
  protectedUploads: uploadSchema({
    image: { maxSize: "5MB", maxCount: 10 },
  }).middleware(async ({ req }) => {
    // Validate CSRF token
    const csrfToken = req.headers.get("x-csrf-token");
    if (!csrf.verify(csrfToken)) {
      throw new Error("Invalid CSRF token");
    }

    return { userId: await getUserId(req) };
  }),
});

// Client-side CSRF token
export const upload = createUploadClient<AppRouter>({
  endpoint: "/api/upload",
  headers: {
    "X-CSRF-Token": getCsrfToken(),
  },
});

Environment-Specific Security

export const router = createUploadRouter({
  devUploads: uploadSchema({
    any: { maxSize: "100MB", maxCount: 100 }
  }).middleware(async ({ req }) => {
    if (process.env.NODE_ENV !== 'development') {
      throw new Error("Development endpoint only")
    }
    
    // Relaxed auth for development
    const userId = req.headers.get('x-dev-user-id') || 'dev-user'
    return { userId }
  })
})
export const router = createUploadRouter({
  stagingUploads: uploadSchema({
    any: { maxSize: "50MB", maxCount: 20 }
  }).middleware(async ({ req }) => {
    // Basic auth for staging
    const token = req.headers.get('authorization')
    if (!token || !await validateStagingToken(token)) {
      throw new Error("Invalid staging credentials")
    }
    
    return { userId: 'staging-user' }
  })
})
export const router = createUploadRouter({
  prodUploads: uploadSchema({
    any: { maxSize: "25MB", maxCount: 10 }
  }).middleware(async ({ req }) => {
    // Full security stack for production
    const session = await getServerSession(req)
    if (!session) throw new Error("Authentication required")
    
    const ip = req.headers.get('x-forwarded-for')
    await checkIPWhitelist(ip)
    
    const { success } = await ratelimit.limit(session.user.id)
    if (!success) throw new Error("Rate limit exceeded")
    
    await auditLog('file_upload_attempt', {
      userId: session.user.id,
      ip,
      timestamp: new Date()
    })
    
    return {
      userId: session.user.id,
      auditId: generateAuditId()
    }
  })
})

Security Middleware Examples

Multi-Factor Authentication

export const router = createUploadRouter({
  sensitiveUploads: uploadSchema({
    document: { maxSize: "10MB", maxCount: 1 },
  }).middleware(async ({ req }) => {
    const { userId } = await authenticateUser(req);
    const mfaToken = req.headers.get("x-mfa-token");

    if (!mfaToken) {
      throw new Error("MFA token required for sensitive uploads");
    }

    const isValidMFA = await verifyMFAToken(userId, mfaToken);
    if (!isValidMFA) {
      throw new Error("Invalid MFA token");
    }

    return { userId, mfaVerified: true };
  }),
});

IP Whitelisting

export const router = createUploadRouter({
  restrictedUploads: uploadSchema({
    any: { maxSize: "50MB", maxCount: 5 },
  }).middleware(async ({ req }) => {
    const clientIP =
      req.headers.get("x-forwarded-for") ||
      req.headers.get("x-real-ip") ||
      "unknown";

    const allowedIPs = process.env.ALLOWED_IPS?.split(",") || [];

    if (!allowedIPs.includes(clientIP)) {
      throw new Error(`Access denied for IP: ${clientIP}`);
    }

    return { userId: await getUserId(req), clientIP };
  }),
});

Content Scanning

import { scanFile } from "@/lib/virus-scanner";

export const router = createUploadRouter({
  scannedUploads: uploadSchema({
    any: { maxSize: "25MB", maxCount: 10 },
  }).middleware(async ({ req, files }) => {
    // Scan all files for malware
    for (const file of files) {
      const scanResult = await scanFile(file);

      if (scanResult.threat) {
        await logSecurityEvent({
          type: "malware_detected",
          filename: file.name,
          threat: scanResult.threat,
          ip: req.headers.get("x-forwarded-for"),
        });

        throw new Error("File contains malicious content");
      }
    }

    return { userId: await getUserId(req) };
  }),
});

Security First: Always implement multiple layers of security. Authentication, authorization, input validation, and monitoring work together to protect your application.