# Security Best Practices Guide ## ๐Ÿ” Authentication & Authorization ### Current Issues The application currently stores sensitive data in localStorage and has incomplete authentication checks. ### Recommended Implementation #### 1. Secure Token Storage **โŒ Current (Insecure):** ```javascript // src/helper/accountContext/AccountProvider.js localStorage.setItem("role", JSON.stringify(data?.role)) ``` **โœ… Recommended:** ```javascript // Use httpOnly cookies set by the server // Client-side code should NOT directly access tokens // Server-side API route (example) export async function POST(request) { const { email, password } = await request.json(); // Authenticate user const user = await authenticateUser(email, password); if (user) { // Set httpOnly cookie const response = NextResponse.json({ success: true, user }); response.cookies.set('auth_token', user.token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 60 * 60 * 24 * 7, // 1 week path: '/' }); return response; } return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }); } ``` #### 2. Client-Side Permission Checks **โŒ Current (Incomplete):** ```javascript // src/layout/index.js useEffect(() => { const securePaths = mounted && ConvertPermissionArr(data1?.permissions); if (mounted && !securePaths.find((item) => item?.name == replacePath(path?.split("/")[2]))) { router.push(`/403`); } }, [data1]); ``` **โœ… Recommended:** ```javascript // Create a custom hook for permission checks // src/utils/hooks/usePermission.js import { useContext, useEffect, useState } from 'react'; import { useRouter, usePathname } from 'next/navigation'; import AccountContext from '@/helper/accountContext'; export function usePermission(requiredPermission) { const { accountData } = useContext(AccountContext); const router = useRouter(); const pathname = usePathname(); const [isAuthorized, setIsAuthorized] = useState(false); const [isLoading, setIsLoading] = useState(true); useEffect(() => { if (!accountData) { setIsLoading(true); return; } const userPermissions = accountData?.permissions || []; const hasPermission = userPermissions.some( (perm) => perm.name === requiredPermission ); if (!hasPermission) { const lang = pathname.split('/')[1] || 'en'; router.push(`/${lang}/403`); setIsAuthorized(false); } else { setIsAuthorized(true); } setIsLoading(false); }, [accountData, requiredPermission, router, pathname]); return { isAuthorized, isLoading }; } // Usage in components function ProductPage() { const { isAuthorized, isLoading } = usePermission('product.view'); if (isLoading) { return ; } if (!isAuthorized) { return null; // Will redirect via hook } return ; } ``` --- ## ๐Ÿ›ก๏ธ XSS Prevention ### HTML Sanitization **โŒ Current (Vulnerable):** ```javascript // src/utils/customFunctions/TextLimit.js const sanitizeAndTrustHtml = (htmlString) => { return { __html: htmlString }; // NO SANITIZATION! }; ``` **โœ… Recommended:** ```javascript import DOMPurify from 'isomorphic-dompurify'; const sanitizeAndTrustHtml = (htmlString) => { // Configure DOMPurify for your needs const config = { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'], ALLOWED_ATTR: ['href', 'target'], ALLOW_DATA_ATTR: false }; return { __html: DOMPurify.sanitize(htmlString, config) }; }; // Even better: avoid dangerouslySetInnerHTML when possible const TextLimit = ({ value, maxLength }) => { if (!value) return ''; let summarizedValue = value.substring(0, maxLength); if (value.length > maxLength) { summarizedValue += '...'; } // If it's plain text, just render it if (!containsHtmlTags(value)) { return
{summarizedValue}
; } // Only use dangerouslySetInnerHTML for trusted, sanitized content const sanitizedValue = sanitizeAndTrustHtml(summarizedValue); return
; }; ``` ### User Input Validation **โœ… Always validate and sanitize user input:** ```javascript // src/utils/validation/inputSanitization.js export function sanitizeInput(input) { if (typeof input !== 'string') return input; // Remove potentially dangerous characters return input .replace(/[<>]/g, '') // Remove < and > .trim(); } export function validateEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } export function sanitizeFileName(filename) { // Remove path traversal attempts return filename .replace(/\.\./g, '') .replace(/[\/\\]/g, '') .trim(); } // Usage in forms function handleSubmit(values) { const sanitizedValues = { name: sanitizeInput(values.name), email: values.email, // Already validated by Yup description: sanitizeInput(values.description) }; // Submit sanitized values await submitForm(sanitizedValues); } ``` --- ## ๐Ÿ”’ API Security ### Request Validation **โœ… Implement proper request validation:** ```javascript // src/app/api/products/route.js import { NextResponse } from 'next/server'; import { z } from 'zod'; // Define schema for validation const productSchema = z.object({ name: z.string().min(1).max(255), price: z.number().positive(), description: z.string().max(5000).optional(), category_id: z.number().int().positive() }); export async function POST(request) { try { // Verify authentication const token = request.cookies.get('auth_token')?.value; if (!token) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } // Parse and validate request body const body = await request.json(); const validatedData = productSchema.parse(body); // Check permissions const user = await verifyToken(token); if (!user.permissions.includes('product.create')) { return NextResponse.json( { error: 'Forbidden' }, { status: 403 } ); } // Process request const product = await createProduct(validatedData); return NextResponse.json({ success: true, product }); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( { error: 'Validation failed', details: error.errors }, { status: 400 } ); } console.error('API Error:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); } } ``` ### Rate Limiting **โœ… Implement rate limiting:** ```javascript // src/middleware.js import { Ratelimit } from '@upstash/ratelimit'; import { Redis } from '@upstash/redis'; const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, '10 s'), }); export async function middleware(request) { // Rate limit API routes if (request.nextUrl.pathname.startsWith('/api/')) { const ip = request.ip ?? '127.0.0.1'; const { success, limit, reset, remaining } = await ratelimit.limit(ip); if (!success) { return NextResponse.json( { error: 'Too many requests' }, { status: 429, headers: { 'X-RateLimit-Limit': limit.toString(), 'X-RateLimit-Remaining': remaining.toString(), 'X-RateLimit-Reset': reset.toString() } } ); } } return NextResponse.next(); } ``` --- ## ๐Ÿ” Environment Variables ### Secure Configuration **โŒ Current:** ```javascript // next.config.js env: { API_PROD_URL: "https://fastkart-admin-json.vercel.app/api/", } ``` **โœ… Recommended:** Create `.env.local`: ```env # Public variables (accessible in browser) NEXT_PUBLIC_API_URL=http://localhost:3000/api NEXT_PUBLIC_APP_NAME=FastKart Admin # Private variables (server-side only) DATABASE_URL=postgresql://user:pass@localhost:5432/db JWT_SECRET=your-super-secret-key-change-this API_SECRET_KEY=another-secret-key # Third-party services STRIPE_SECRET_KEY=sk_test_... SENDGRID_API_KEY=SG... ``` Create `.env.production`: ```env NEXT_PUBLIC_API_URL=https://api.fastkart.com DATABASE_URL=postgresql://prod-user:prod-pass@prod-host:5432/prod-db JWT_SECRET=production-secret-key ``` Update `next.config.js`: ```javascript /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, // Remove hardcoded env variables // They will be loaded from .env files automatically images: { remotePatterns: [ { protocol: 'https', hostname: 'fastkart-admin-json.vercel.app', }, ], }, }; module.exports = nextConfig; ``` **โš ๏ธ Important:** Add to `.gitignore`: ```gitignore .env .env*.local .env.production ``` --- ## ๐Ÿ› ๏ธ Error Handling ### Centralized Error Handler **โœ… Create a centralized error handler:** ```javascript // src/utils/errorHandler.js import { toast } from 'react-toastify'; export class AppError extends Error { constructor(message, statusCode = 500, isOperational = true) { super(message); this.statusCode = statusCode; this.isOperational = isOperational; Error.captureStackTrace(this, this.constructor); } } export function handleApiError(error) { // Log error for debugging (use proper logging service in production) if (process.env.NODE_ENV === 'development') { console.error('API Error:', error); } // Send to error tracking service (Sentry, etc.) if (process.env.NODE_ENV === 'production') { // Sentry.captureException(error); } // User-friendly error messages const status = error?.response?.status; const message = error?.response?.data?.message; switch (status) { case 400: toast.error(message || 'Invalid request. Please check your input.'); break; case 401: toast.error('Your session has expired. Please login again.'); // Redirect to login window.location.href = '/en/auth/login'; break; case 403: toast.error('You do not have permission to perform this action.'); break; case 404: toast.error('The requested resource was not found.'); break; case 422: toast.error(message || 'Validation error. Please check your input.'); break; case 429: toast.error('Too many requests. Please try again later.'); break; case 500: case 502: case 503: toast.error('Server error. Please try again later.'); break; default: toast.error(message || 'An unexpected error occurred.'); } return Promise.reject(error); } // Usage in axios interceptor // src/utils/axiosUtils/index.js import { handleApiError } from '../errorHandler'; const client = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL, headers: { Accept: "application/json", }, }); client.interceptors.response.use( (response) => response, (error) => handleApiError(error) ); ``` --- ## ๐Ÿ“ Logging ### Structured Logging **โœ… Implement proper logging:** ```javascript // src/utils/logger.js const isDevelopment = process.env.NODE_ENV === 'development'; class Logger { info(message, meta = {}) { if (isDevelopment) { console.log('[INFO]', message, meta); } // Send to logging service in production } warn(message, meta = {}) { if (isDevelopment) { console.warn('[WARN]', message, meta); } // Send to logging service } error(message, error, meta = {}) { if (isDevelopment) { console.error('[ERROR]', message, error, meta); } // Send to error tracking service (Sentry, etc.) } debug(message, meta = {}) { if (isDevelopment) { console.debug('[DEBUG]', message, meta); } } } export const logger = new Logger(); // Usage import { logger } from '@/utils/logger'; function fetchProducts() { logger.info('Fetching products', { page: 1, limit: 10 }); try { const products = await api.getProducts(); logger.info('Products fetched successfully', { count: products.length }); return products; } catch (error) { logger.error('Failed to fetch products', error, { page: 1 }); throw error; } } ``` --- ## ๐Ÿงช Testing Security ### Security Test Examples ```javascript // __tests__/security/xss.test.js import { render, screen } from '@testing-library/react'; import TextLimit from '@/utils/customFunctions/TextLimit'; describe('XSS Prevention', () => { it('should sanitize malicious HTML', () => { const maliciousInput = 'Hello'; render(); // Script tag should be removed expect(screen.queryByText(/script/i)).not.toBeInTheDocument(); expect(screen.getByText(/Hello/i)).toBeInTheDocument(); }); it('should handle img onerror attacks', () => { const maliciousInput = ''; render(); // onerror should be removed const img = screen.queryByRole('img'); expect(img?.getAttribute('onerror')).toBeNull(); }); }); // __tests__/security/auth.test.js describe('Authentication', () => { it('should redirect unauthenticated users', async () => { // Mock no auth token document.cookie = 'auth_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC'; const { result } = renderHook(() => usePermission('product.view')); await waitFor(() => { expect(result.current.isAuthorized).toBe(false); }); }); }); ``` --- ## ๐Ÿ“š Additional Resources - [OWASP Top 10](https://owasp.org/www-project-top-ten/) - [Next.js Security Headers](https://nextjs.org/docs/app/api-reference/next-config-js/headers) - [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) - [DOMPurify Documentation](https://github.com/cure53/DOMPurify) --- **Last Updated:** January 2026