Frontend Security Vulnerabilities: Complete Guide with Examples and Prevention
Frontend Security Vulnerabilities
Complete Guide with Examples and Prevention
Frontend security is often overlooked, yet it's the first line of defense against cyber attacks. This comprehensive guide covers 8 critical vulnerabilities with practical examples and actionable solutions.
8 Critical Vulnerabilities
XSS, CSRF, Authentication flaws, and more
Practical Examples
Real code examples with vulnerable and secure implementations
Actionable Solutions
Step-by-step prevention techniques and best practices
Table of Contents
XSS (Cross-Site Scripting) Vulnerabilities
CSRF (Cross-Site Request Forgery) Attacks
Content Security Policy (CSP) Setup
Authentication & Authorization Flaws
Input Validation & Sanitization
Secure vs Insecure Data Storage
HTTPS/TLS Implementation
Dependency Vulnerability Scanning
XSS Vulnerabilities
Cross-Site Scripting attacks and prevention
XSS attacks inject malicious scripts into web applications, allowing attackers to steal user data, hijack sessions, or perform actions on behalf of users. This is one of the most common and dangerous frontend security vulnerabilities.
Vulnerable Code Example
// β VULNERABLE: Direct innerHTML injection
function displayUserComment(comment) {
document.getElementById('comments').innerHTML += `
<div class="comment">${comment}</div>
`;
}
// β React - Dangerous HTML injection
function CommentComponent({ comment }) {
return <div dangerouslySetInnerHTML={{ __html: comment }} />;
}
Secure Implementation
// β
SECURE: Use textContent instead of innerHTML
function displayUserComment(comment) {
const commentDiv = document.createElement('div');
commentDiv.className = 'comment';
commentDiv.textContent = comment; // Automatically escapes HTML
document.getElementById('comments').appendChild(commentDiv);
}
// β
React - Safe text rendering
function CommentComponent({ comment }) {
return <div className="comment">{comment}</div>; // React escapes by default
}
// β
For rich text, use a sanitization library
import DOMPurify from 'dompurify';
function SafeRichComment({ htmlContent }) {
const sanitizedContent = DOMPurify.sanitize(htmlContent);
return <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />;
}
CSRF Protection
Cross-Site Request Forgery attacks and prevention
CSRF attacks trick users into performing unwanted actions on applications where they're authenticated. This is particularly dangerous for state-changing operations like money transfers or data modifications.
Vulnerable Implementation
// β VULNERABLE: No CSRF protection
async function transferMoney(toAccount, amount) {
const response = await fetch('/api/transfer', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ toAccount, amount })
});
return response.json();
}
Secure Implementation
// β
SECURE: CSRF token implementation
async function transferMoney(toAccount, amount) {
// Get CSRF token from meta tag or API
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
const response = await fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
'X-Requested-With': 'XMLHttpRequest' // Additional protection
},
credentials: 'same-origin', // Include cookies for same-origin requests
body: JSON.stringify({ toAccount, amount })
});
if (!response.ok) {
throw new Error('Transfer failed');
}
return response.json();
}
// β
React Hook for CSRF protection
function useCSRFToken() {
const [token, setToken] = useState('');
useEffect(() => {
// Fetch CSRF token on component mount
fetch('/api/csrf-token')
.then(res => res.json())
.then(data => setToken(data.token));
}, []);
return token;
}
Content Security Policy
CSP implementation and configuration
CSP is a security layer that helps detect and mitigate XSS and data injection attacks by controlling which resources the browser is allowed to load. It's one of the most effective defenses against client-side attacks.
Basic CSP Configuration
<!-- HTML Meta Tag -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;">
// Next.js Configuration
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' blob: data:",
"font-src 'self'",
"connect-src 'self'"
].join('; ')
}
]
}
];
}
};
Advanced CSP with Nonce
// Generate unique nonce for each request
function generateNonce() {
return crypto.randomBytes(16).toString('base64');
}
// CSP with nonce
const nonce = generateNonce();
const csp = `
default-src 'self';
script-src 'self' 'nonce-${nonce}';
style-src 'self' 'nonce-${nonce}';
`;
// Use nonce in scripts
<script nonce={${nonce}}>
// Your inline script here
</script>
4. π Authentication & Authorization Vulnerabilities
Weak authentication mechanisms can lead to unauthorized access, session hijacking, and privilege escalation.
Insecure Authentication
// β VULNERABLE: Weak password validation
function validatePassword(password) {
return password.length >= 6; // Too weak!
}
// β Storing tokens in localStorage (XSS vulnerable)
localStorage.setItem('authToken', token);
// β No token expiration check
function isAuthenticated() {
return !!localStorage.getItem('authToken');
}
Secure Authentication
// β
SECURE: Strong password validation
function validatePassword(password) {
const minLength = 12;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return password.length >= minLength &&
hasUpperCase && hasLowerCase &&
hasNumbers && hasSpecialChar;
}
// β
Secure token storage using httpOnly cookies
function setAuthToken(token) {
// Set via server-side with httpOnly, secure, sameSite flags
document.cookie = `authToken=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`;
}
// β
JWT token validation with expiration
function isAuthenticated() {
const token = getTokenFromSecureCookie();
if (!token) return false;
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.exp * 1000 > Date.now(); // Check expiration
} catch {
return false;
}
}
// β
Role-based access control
function hasPermission(user, resource, action) {
return user.roles.some(role =>
role.permissions.includes(`${resource}:${action}`)
);
}
5. β Input Validation & Sanitization
Proper input validation prevents injection attacks and ensures data integrity.
Comprehensive Input Validation
// β
Email validation with regex and DNS check
function validateEmail(email) {
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
if (!emailRegex.test(email)) return false;
// Additional checks
const domain = email.split('@')[1];
return domain.length > 0 && !domain.includes('..');
}
// β
SQL injection prevention for search queries
function sanitizeSearchQuery(query) {
return query
.replace(/[<>]/g, '') // Remove angle brackets
.replace(/['";]/g, '') // Remove quotes and semicolons
.trim()
.substring(0, 100); // Limit length
}
// β
File upload validation
function validateFileUpload(file) {
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
const maxSize = 5 * 1024 * 1024; // 5MB
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif'];
const fileExtension = file.name.toLowerCase().substring(file.name.lastIndexOf('.'));
return allowedTypes.includes(file.type) &&
file.size <= maxSize &&
allowedExtensions.includes(fileExtension);
}
// β
React form validation with Zod
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(2).max(50).regex(/^[a-zA-Zs]+$/),
email: z.string().email(),
age: z.number().min(13).max(120),
website: z.string().url().optional()
});
function validateUserData(data) {
try {
return userSchema.parse(data);
} catch (error) {
throw new Error(`Validation failed: ${error.message}`);
}
}
6. πΎ Secure vs Insecure Data Storage
Proper data storage practices protect sensitive information from unauthorized access.
Storage Security Comparison
Insecure Storage
// Storing sensitive data in localStorage
localStorage.setItem('creditCard', '4111-1111-1111-1111');
localStorage.setItem('ssn', '123-45-6789');
// Storing passwords in plain text
const user = {
password: 'mypassword123',
apiKey: 'sk-1234567890abcdef'
};
Secure Storage
// Use sessionStorage for temporary data
sessionStorage.setItem('tempData', encryptedData);
// Store sensitive data server-side only
const user = {
hashedPassword: await bcrypt.hash(password, 12),
// API keys stored server-side
};
// Encrypt sensitive localStorage data
const encrypted = CryptoJS.AES.encrypt(
JSON.stringify(data),
userSessionKey
).toString();
Encryption Implementation
// β
Client-side encryption for non-sensitive cached data
import CryptoJS from 'crypto-js';
class SecureStorage {
constructor() {
this.key = this.generateSessionKey();
}
generateSessionKey() {
return CryptoJS.lib.WordArray.random(256/8).toString();
}
encrypt(data) {
return CryptoJS.AES.encrypt(JSON.stringify(data), this.key).toString();
}
decrypt(encryptedData) {
const bytes = CryptoJS.AES.decrypt(encryptedData, this.key);
return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
}
setItem(key, value) {
const encrypted = this.encrypt(value);
sessionStorage.setItem(key, encrypted);
}
getItem(key) {
const encrypted = sessionStorage.getItem(key);
return encrypted ? this.decrypt(encrypted) : null;
}
}
7. π HTTPS/TLS Security Implementation
HTTPS encrypts data in transit and prevents man-in-the-middle attacks.
HTTPS Configuration
// β
Force HTTPS redirect (Next.js)
module.exports = {
async redirects() {
return [
{
source: '/(.*)',
has: [
{
type: 'header',
key: 'x-forwarded-proto',
value: 'http',
},
],
destination: 'https://yourdomain.com/:path*',
permanent: true,
},
];
},
};
// β
Security headers
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
}
]
}
];
}
};
// β
Secure API calls
async function secureApiCall(endpoint, data) {
const response = await fetch(`https://api.yourdomain.com${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await getSecureToken()}`
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`API call failed: ${response.status}`);
}
return response.json();
}
Dependency Vulnerability Scanning
Automated security scanning and dependency management
Regular dependency scanning helps identify and fix security vulnerabilities in third-party packages. This is crucial for maintaining a secure application as vulnerabilities are discovered frequently.
Automated Security Scanning
// package.json scripts for security
{
"scripts": {
"audit": "npm audit",
"audit:fix": "npm audit fix",
"security:check": "npm audit --audit-level moderate",
"snyk:test": "snyk test",
"snyk:monitor": "snyk monitor"
},
"husky": {
"hooks": {
"pre-commit": "npm run security:check"
}
}
}
// GitHub Actions workflow for security scanning
name: Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run npm audit
run: npm audit --audit-level moderate
- name: Run Snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
Dependency Management Best Practices
// β
Lock dependency versions
{
"dependencies": {
"react": "18.2.0", // Exact version
"lodash": "~4.17.21", // Patch updates only
"express": "^4.18.2" // Minor updates allowed
}
}
// β
Regular dependency updates
npm update
npm outdated
// β
Use .nvmrc for Node.js version consistency
echo "18.17.0" > .nvmrc
// β
Renovate bot configuration
{
"extends": ["config:base"],
"schedule": ["before 4am on Monday"],
"packageRules": [
{
"updateTypes": ["patch"],
"automerge": true
}
]
}
Security Checklist
Essential security measures to implement
Authentication & Authorization
- β Strong password requirements
- β Multi-factor authentication
- β Secure token storage
- β Session timeout
- β Role-based access control
Input & Output
- β Input validation & sanitization
- β Output encoding
- β File upload restrictions
- β SQL injection prevention
- β XSS protection
Network Security
- β HTTPS enforcement
- β CSRF protection
- β CORS configuration
- β Security headers
- β Content Security Policy
Dependencies & Monitoring
- β Regular security audits
- β Dependency updates
- β Vulnerability scanning
- β Error logging
- β Performance monitoring
Practical Examples Repository
All the examples in this article are available as working demos in my GitHub repository. Each vulnerability includes both vulnerable and secure implementations with detailed explanations.
Conclusion
Key takeaways and next steps
Frontend security is an ongoing process that requires constant vigilance and regular updates. By implementing these security measures and following best practices, you can significantly reduce the risk of security vulnerabilities in your web applications.
Remember that security is not a one-time implementation but a continuous process. Stay updated with the latest security threats, regularly audit your code, and always validate both frontend and backend implementations.
π Regular Audits
Schedule regular security audits and vulnerability assessments to identify and fix issues early.
π Stay Updated
Keep up with the latest security trends, vulnerabilities, and best practices in web development.