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)
Prop | Type | Default |
---|---|---|
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.