[{"data":1,"prerenderedAt":417},["ShallowReactive",2],{"blog-best-practices/api-design":3},{"id":4,"title":5,"body":6,"category":391,"date":392,"dateModified":393,"description":394,"draft":395,"extension":396,"faq":397,"featured":395,"headerVariant":402,"image":403,"keywords":403,"meta":404,"navigation":405,"ogDescription":406,"ogTitle":403,"path":407,"readTime":408,"schemaOrg":409,"schemaType":410,"seo":411,"sitemap":412,"stem":413,"tags":414,"twitterCard":415,"__hash__":416},"blog/blog/best-practices/api-design.md","API Security Best Practices: Authentication, Validation, and Rate Limiting",{"type":7,"value":8,"toc":376},"minimark",[9,16,25,30,35,57,61,64,79,83,86,95,99,102,111,115,118,127,131,134,143,147,150,159,169,173,176,185,189,192,201,205,283,289,317,321,324,345,364],[10,11,12],"tldr",{},[13,14,15],"p",{},"The #1 API security best practice is defense in depth. Authenticate every request, authorize access to specific resources, validate all inputs, rate limit to prevent abuse, and never expose sensitive data in responses. These practices prevent 88% of API security incidents. Total time to implement: ~36 minutes.",[17,18,19],"quotable-box",{},[20,21,22],"blockquote",{},[13,23,24],{},"\"APIs are the front doors, back doors, and windows of modern applications. Lock every single one of them.\"",[26,27,29],"h2",{"id":28},"the-api-security-checklist","The API Security Checklist",[31,32,34],"h4",{"id":33},"every-api-endpoint-should","Every API endpoint should:",[36,37,38,42,45,48,51,54],"ul",{},[39,40,41],"li",{},"Verify authentication (who is the user?)",[39,43,44],{},"Check authorization (can they access this resource?)",[39,46,47],{},"Validate input data (is the request well-formed?)",[39,49,50],{},"Sanitize output (does the response contain sensitive data?)",[39,52,53],{},"Log the request (for auditing and debugging)",[39,55,56],{},"Handle errors gracefully (without leaking information)",[26,58,60],{"id":59},"best-practice-1-authenticate-every-request-5-min","Best Practice 1: Authenticate Every Request 5 min",[13,62,63],{},"Never trust that a request is authenticated. Verify on every call:",[65,66,68],"code-block",{"label":67},"Express middleware for authentication",[69,70,75],"pre",{"className":71,"code":73,"language":74},[72],"language-text","import jwt from 'jsonwebtoken';\n\nasync function authenticate(req, res, next) {\n  const authHeader = req.headers.authorization;\n\n  if (!authHeader?.startsWith('Bearer ')) {\n    return res.status(401).json({ error: 'Authentication required' });\n  }\n\n  const token = authHeader.substring(7);\n\n  try {\n    const decoded = jwt.verify(token, process.env.JWT_SECRET);\n    req.user = decoded;\n    next();\n  } catch (error) {\n    return res.status(401).json({ error: 'Invalid token' });\n  }\n}\n\n// Apply to protected routes\napp.use('/api/protected', authenticate);\n","text",[76,77,73],"code",{"__ignoreMap":78},"",[26,80,82],{"id":81},"best-practice-2-implement-authorization-5-min","Best Practice 2: Implement Authorization 5 min",[13,84,85],{},"Authentication is not enough. Verify the user can access the specific resource:",[65,87,89],{"label":88},"Resource-level authorization",[69,90,93],{"className":91,"code":92,"language":74},[72],"app.get('/api/posts/:id', authenticate, async (req, res) => {\n  const post = await db.post.findUnique({\n    where: { id: req.params.id }\n  });\n\n  if (!post) {\n    return res.status(404).json({ error: 'Not found' });\n  }\n\n  // Authorization: Check if user can access this post\n  if (post.authorId !== req.user.id && !post.isPublished) {\n    return res.status(403).json({ error: 'Access denied' });\n  }\n\n  res.json(post);\n});\n\napp.put('/api/posts/:id', authenticate, async (req, res) => {\n  const post = await db.post.findUnique({\n    where: { id: req.params.id }\n  });\n\n  if (!post) {\n    return res.status(404).json({ error: 'Not found' });\n  }\n\n  // Only author can update\n  if (post.authorId !== req.user.id) {\n    return res.status(403).json({ error: 'Access denied' });\n  }\n\n  // Proceed with update...\n});\n",[76,94,92],{"__ignoreMap":78},[26,96,98],{"id":97},"best-practice-3-validate-all-input-5-min","Best Practice 3: Validate All Input 5 min",[13,100,101],{},"Never trust client data. Validate everything:",[65,103,105],{"label":104},"Input validation with Zod",[69,106,109],{"className":107,"code":108,"language":74},[72],"import { z } from 'zod';\n\nconst createPostSchema = z.object({\n  title: z.string().min(3).max(200),\n  content: z.string().max(50000),\n  tags: z.array(z.string()).max(10).optional(),\n});\n\napp.post('/api/posts', authenticate, async (req, res) => {\n  // Validate input\n  const result = createPostSchema.safeParse(req.body);\n\n  if (!result.success) {\n    return res.status(400).json({\n      error: 'Invalid input',\n      details: result.error.issues.map(i => ({\n        field: i.path.join('.'),\n        message: i.message,\n      })),\n    });\n  }\n\n  // Use validated data\n  const post = await db.post.create({\n    data: {\n      ...result.data,\n      authorId: req.user.id, // Never use client-provided user ID\n    },\n  });\n\n  res.status(201).json(post);\n});\n",[76,110,108],{"__ignoreMap":78},[26,112,114],{"id":113},"best-practice-4-use-parameterized-queries-3-min","Best Practice 4: Use Parameterized Queries 3 min",[13,116,117],{},"Prevent SQL injection by using parameterized queries or ORMs:",[65,119,121],{"label":120},"SQL injection prevention",[69,122,125],{"className":123,"code":124,"language":74},[72],"// WRONG: SQL injection vulnerability\nconst query = `SELECT * FROM users WHERE id = ${userId}`;\n\n// CORRECT: Parameterized query\nconst result = await db.query(\n  'SELECT * FROM users WHERE id = $1',\n  [userId]\n);\n\n// ALSO CORRECT: Using an ORM like Prisma\nconst user = await prisma.user.findUnique({\n  where: { id: userId }\n});\n",[76,126,124],{"__ignoreMap":78},[26,128,130],{"id":129},"best-practice-5-rate-limit-your-api-5-min","Best Practice 5: Rate Limit Your API 5 min",[13,132,133],{},"Prevent abuse and brute force attacks with rate limiting:",[65,135,137],{"label":136},"Rate limiting with express-rate-limit",[69,138,141],{"className":139,"code":140,"language":74},[72],"import rateLimit from 'express-rate-limit';\n\n// General API rate limit\nconst apiLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  max: 100, // 100 requests per window\n  message: { error: 'Too many requests' },\n  standardHeaders: true,\n});\n\n// Stricter limit for auth endpoints\nconst authLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5, // Only 5 attempts\n  message: { error: 'Too many login attempts' },\n});\n\n// Even stricter for password reset\nconst passwordResetLimiter = rateLimit({\n  windowMs: 60 * 60 * 1000, // 1 hour\n  max: 3,\n  message: { error: 'Too many password reset requests' },\n});\n\napp.use('/api/', apiLimiter);\napp.use('/api/auth/login', authLimiter);\napp.use('/api/auth/reset-password', passwordResetLimiter);\n",[76,142,140],{"__ignoreMap":78},[26,144,146],{"id":145},"best-practice-6-handle-errors-safely-5-min","Best Practice 6: Handle Errors Safely 5 min",[13,148,149],{},"Error messages should not expose internal details:",[65,151,153],{"label":152},"Safe error handling",[69,154,157],{"className":155,"code":156,"language":74},[72],"// Global error handler\napp.use((err, req, res, next) => {\n  // Log full error for debugging\n  console.error('API Error:', {\n    error: err.message,\n    stack: err.stack,\n    path: req.path,\n    method: req.method,\n    userId: req.user?.id,\n  });\n\n  // Send generic message to client\n  if (err.name === 'ValidationError') {\n    return res.status(400).json({ error: 'Invalid request data' });\n  }\n\n  if (err.name === 'UnauthorizedError') {\n    return res.status(401).json({ error: 'Authentication required' });\n  }\n\n  // Generic error for unexpected issues\n  res.status(500).json({ error: 'An unexpected error occurred' });\n});\n",[76,158,156],{"__ignoreMap":78},[160,161,162],"warning-box",{},[13,163,164,168],{},[165,166,167],"strong",{},"Never expose:"," Stack traces, database errors, file paths, or internal system details in API responses. These help attackers understand your system.",[26,170,172],{"id":171},"best-practice-7-sanitize-response-data-5-min","Best Practice 7: Sanitize Response Data 5 min",[13,174,175],{},"Remove sensitive fields before sending responses:",[65,177,179],{"label":178},"Response sanitization",[69,180,183],{"className":181,"code":182,"language":74},[72],"// Define what fields to expose\nfunction sanitizeUser(user) {\n  return {\n    id: user.id,\n    name: user.name,\n    email: user.email,\n    createdAt: user.createdAt,\n    // Never include: password, tokens, internal IDs\n  };\n}\n\napp.get('/api/users/:id', authenticate, async (req, res) => {\n  const user = await db.user.findUnique({\n    where: { id: req.params.id }\n  });\n\n  if (!user) {\n    return res.status(404).json({ error: 'Not found' });\n  }\n\n  // Sanitize before sending\n  res.json(sanitizeUser(user));\n});\n\n// Or use Prisma select to only fetch needed fields\nconst user = await prisma.user.findUnique({\n  where: { id: userId },\n  select: {\n    id: true,\n    name: true,\n    email: true,\n    // password is never selected\n  },\n});\n",[76,184,182],{"__ignoreMap":78},[26,186,188],{"id":187},"best-practice-8-use-https-only-3-min","Best Practice 8: Use HTTPS Only 3 min",[13,190,191],{},"All API traffic should be encrypted:",[65,193,195],{"label":194},"Enforce HTTPS",[69,196,199],{"className":197,"code":198,"language":74},[72],"// Redirect HTTP to HTTPS\napp.use((req, res, next) => {\n  if (req.headers['x-forwarded-proto'] !== 'https' && process.env.NODE_ENV === 'production') {\n    return res.redirect(`https://${req.hostname}${req.url}`);\n  }\n  next();\n});\n\n// Add security headers\napp.use((req, res, next) => {\n  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');\n  next();\n});\n",[76,200,198],{"__ignoreMap":78},[26,202,204],{"id":203},"common-api-security-mistakes","Common API Security Mistakes",[206,207,208,224],"table",{},[209,210,211],"thead",{},[212,213,214,218,221],"tr",{},[215,216,217],"th",{},"Mistake",[215,219,220],{},"Impact",[215,222,223],{},"Prevention",[225,226,227,239,250,261,272],"tbody",{},[212,228,229,233,236],{},[230,231,232],"td",{},"Missing authentication",[230,234,235],{},"Unauthorized access",[230,237,238],{},"Auth middleware on all routes",[212,240,241,244,247],{},[230,242,243],{},"No authorization checks",[230,245,246],{},"Data from other users",[230,248,249],{},"Verify resource ownership",[212,251,252,255,258],{},[230,253,254],{},"Trusting client user ID",[230,256,257],{},"Impersonation",[230,259,260],{},"Use server-side session",[212,262,263,266,269],{},[230,264,265],{},"Verbose error messages",[230,267,268],{},"Information disclosure",[230,270,271],{},"Generic client messages",[212,273,274,277,280],{},[230,275,276],{},"No rate limiting",[230,278,279],{},"Brute force, DoS",[230,281,282],{},"Implement rate limits",[284,285,286],"info-box",{},[13,287,288],{},"External Resources:\nFor comprehensive API security guidance, see the\nOWASP API Security Top 10\nand the\nREST Security Cheat Sheet\n. These resources provide industry-standard security recommendations.",[290,291,292,299,305,311],"faq-section",{},[293,294,296],"faq-item",{"question":295},"Should I use JWT or sessions for API auth?",[13,297,298],{},"Both can be secure. JWTs work well for stateless APIs and microservices. Sessions (stored server-side) offer easier revocation. For SPAs, consider HttpOnly cookies with session tokens for better XSS protection.",[293,300,302],{"question":301},"How strict should rate limiting be?",[13,303,304],{},"It depends on your use case. Public APIs: 100-1000 requests/hour. Authenticated APIs: higher limits per user. Auth endpoints: very strict (5-10 attempts/15 minutes). Start strict and relax based on legitimate usage patterns.",[293,306,308],{"question":307},"Should I validate on client and server?",[13,309,310],{},"Yes, both. Client validation improves user experience with instant feedback. Server validation is required for security since client validation can be bypassed. Never skip server-side validation.",[293,312,314],{"question":313},"How do I handle API versioning securely?",[13,315,316],{},"Use URL versioning (/api/v1/) or headers. When deprecating versions, give notice and maintain security patches until end-of-life. Never expose internal version numbers that reveal your technology stack.",[26,318,320],{"id":319},"further-reading","Further Reading",[13,322,323],{},"Put these practices into action with our step-by-step guides.",[36,325,326,333,339],{},[39,327,328],{},[329,330,332],"a",{"href":331},"/blog/how-to/add-security-headers","Add security headers to your app",[39,334,335],{},[329,336,338],{"href":337},"/blog/checklists/pre-deployment-security-checklist","Pre-deployment security checklist",[39,340,341],{},[329,342,344],{"href":343},"/blog/getting-started/first-scan","Run your first security scan",[346,347,348,354,359],"related-articles",{},[349,350],"related-card",{"description":351,"href":352,"title":353},"Secure auth patterns","/blog/best-practices/authentication","Authentication Best Practices",[349,355],{"description":356,"href":357,"title":358},"Prevent API abuse","/blog/best-practices/rate-limiting","Rate Limiting Best Practices",[349,360],{"description":361,"href":362,"title":363},"Validate all user input","/blog/best-practices/input-validation","Input Validation",[365,366,369,373],"cta-box",{"href":367,"label":368},"/","Start Free Scan",[26,370,372],{"id":371},"verify-your-api-security","Verify Your API Security",[13,374,375],{},"Scan your API endpoints for security issues and vulnerabilities.",{"title":78,"searchDepth":377,"depth":377,"links":378},2,[379,380,381,382,383,384,385,386,387,388,389,390],{"id":28,"depth":377,"text":29},{"id":59,"depth":377,"text":60},{"id":81,"depth":377,"text":82},{"id":97,"depth":377,"text":98},{"id":113,"depth":377,"text":114},{"id":129,"depth":377,"text":130},{"id":145,"depth":377,"text":146},{"id":171,"depth":377,"text":172},{"id":187,"depth":377,"text":188},{"id":203,"depth":377,"text":204},{"id":319,"depth":377,"text":320},{"id":371,"depth":377,"text":372},"best-practices","2026-01-19","2026-02-09","Essential API security best practices. Learn authentication patterns, input validation, rate limiting, and error handling for secure REST and GraphQL APIs.",false,"md",[398,399,400,401],{"question":295,"answer":298},{"question":301,"answer":304},{"question":307,"answer":310},{"question":313,"answer":316},"vibe-green",null,{},true,"Build secure APIs with authentication, validation, rate limiting, and proper error handling.","/blog/best-practices/api-design","14 min read","[object Object]","Article",{"title":5,"description":394},{"loc":407},"blog/best-practices/api-design",[],"summary_large_image","7MukyQ9HW7RXLxiTKR8IMSslgFLIXmABvuluU8XTBwM",1775843918547]