What are some best practices for handling sensitive data in JavaScript?
TL;DR
Keep secrets and unnecessary sensitive data out of browser code and Web Storage, minimize how long sensitive values remain in memory, and use HTTPS in transit. Validate input for the application's data rules, then use the defense appropriate to each sink: parameterized queries for SQL, contextual output encoding or safe DOM APIs for HTML, and allow-listed commands or APIs elsewhere. Store server-side secrets in a managed secret store or protected environment variables, and remember that any variable bundled into client JavaScript is public.
Best practices for handling sensitive data in JavaScript
Avoid client-side storage for sensitive data
Storing sensitive data such as tokens, passwords, or personal information in client-side storage like localStorage or sessionStorage is risky because it can be easily accessed by malicious scripts. Instead, use secure cookies with the HttpOnly and Secure flags.
// Example of setting a secure cookie in an Express.js serverres.cookie('session', sessionId, {httpOnly: true,secure: true,sameSite: 'lax',});
Use HTTPS
Always use HTTPS to encrypt data in transit between the client and server. This ensures that sensitive data is not exposed to eavesdroppers.
Implement proper authentication and authorization
Ensure that your application has robust authentication and authorization mechanisms. Use libraries and frameworks that are well-tested and maintained.
// Example of using JSON Web Tokens (JWT) for authenticationconst jwt = require('jsonwebtoken');const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, {expiresIn: '1h',});
Validate input and protect each output sink
Validation enforces the expected type, length, range, and format, but there is no universal “sanitize” operation that prevents every injection class. Use parameterized queries for SQL and render untrusted text with textContent or a framework's escaped text interpolation. If an application intentionally accepts HTML, sanitize it with a maintained HTML sanitizer and consider Trusted Types and CSP as additional layers.
// Example of input validation using the express-validator libraryconst { body, validationResult } = require('express-validator');app.post('/submit',[body('email').isEmail(), body('password').isLength({ min: 5 })],(req, res) => {const errors = validationResult(req);if (!errors.isEmpty()) {return res.status(400).json({ errors: errors.array() });}// Proceed with handling the request},);
Use environment variables
Server-side secrets can come from protected environment variables, but a managed secret store usually provides better access control, rotation, and auditing. Never expose a secret through a client-side build variable: values embedded in a browser bundle can be read by every user.
// Example of accessing environment variables in Node.jsconst dbPassword = process.env.DB_PASSWORD;
Regularly update dependencies
Review and update dependencies so known vulnerabilities can be addressed. Audit output requires triage: do not run a forced upgrade blindly if it introduces breaking or unrelated changes.
# Example of running npm auditnpm audit
Further reading
- OWASP Top Ten Security Risks
- MDN Web Docs: HTTPS
- Express.js Security Best Practices
- JSON Web Tokens (JWT) Introduction
- express-validator Documentation