Cookies are small pieces of data that websites store on a user's browser. They're used to maintain state between HTTP requests, track user sessions, and store user preferences. Unlike localStorage, cookies are automatically sent to the server with each HTTP request, making them essential for server-side session management.
In this tutorial, we'll explore cookie creation, manipulation, security considerations, modern alternatives, and best practices for working with cookies in JavaScript.
Understanding Cookies
Cookies are key-value pairs stored in the browser with specific attributes and limitations.
Cookie Basics
// Cookie characteristics
// - Sent to server with each HTTP request
// - Limited size (typically 4KB per cookie)
// - Limited number per domain (usually 20-50)
// - Can have expiration dates
// - Can be restricted to specific paths or domains
// - Can be marked as secure or HTTP-only
// Check if cookies are enabled
function areCookiesEnabled() {
try {
document.cookie = 'testcookie=1';
const enabled = document.cookie.indexOf('testcookie=') !== -1;
document.cookie = 'testcookie=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
return enabled;
} catch (e) {
return false;
}
}
console.log('Cookies enabled:', areCookiesEnabled());
// Basic cookie format
// name=value; expires=date; path=path; domain=domain; secure; HttpOnly; SameSite=Lax/Strict/None
// Example cookie string
document.cookie = 'username=john_doe; expires=Fri, 31 Dec 2023 23:59:59 GMT; path=/; domain=example.com; secure; SameSite=Lax';
Cookie Attributes
// Expires - When cookie expires
// Session cookie (expires when browser closes)
document.cookie = 'session_data=value; path=/';
// Persistent cookie (expires at specific date)
const expirationDate = new Date();
expirationDate.setFullYear(expirationDate.getFullYear() + 1);
document.cookie = `user_id=123; expires=${expirationDate.toUTCString()}; path=/`;
// Max-Age - Seconds until expiration
document.cookie = 'temp_data=value; max-age=3600; path=/'; // Expires in 1 hour
// Path - Restrict to specific path
document.cookie = 'page_pref=value; path=/admin'; // Only accessible on /admin path
document.cookie = 'site_pref=value; path=/'; // Accessible on entire site
// Domain - Restrict to specific domain
document.cookie = 'subdomain=value; domain=blog.example.com'; // Only blog subdomain
document.cookie = 'sitedata=value; domain=example.com'; // All subdomains
// Secure - Only send over HTTPS
document.cookie = 'sensitive=value; secure; path=/'; // Only over HTTPS
// HttpOnly - Not accessible via JavaScript (server-side only)
// This can only be set by server, not JavaScript
// SameSite - CSRF protection
document.cookie = 'token=value; SameSite=Strict; path=/'; // Strict
document.cookie = 'pref=value; SameSite=Lax; path=/'; // Lax (default)
document.cookie = 'analytics=value; SameSite=None; path=/; secure'; // None (requires secure)
Reading and Writing Cookies
Methods for creating, reading, and manipulating cookies in JavaScript.
Setting Cookies
// Basic cookie setting
function setCookie(name, value, days) {
let expires = '';
if (days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = '; expires=' + date.toUTCString();
}
document.cookie = name + '=' + value + expires + '; path=/';
}
// Usage
setCookie('username', 'john_doe', 30);
setCookie('theme', 'dark', 7);
// Advanced cookie setting with options
function setCookieAdvanced(name, value, options = {}) {
const {
expires = null,
maxAge = null,
domain = null,
path = '/',
secure = false,
sameSite = 'Lax'
} = options;
let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
if (expires) {
cookieString += `; expires=${expires.toUTCString()}`;
}
if (maxAge) {
cookieString += `; max-age=${maxAge}`;
}
if (domain) {
cookieString += `; domain=${domain}`;
}
cookieString += `; path=${path}`;
if (secure) {
cookieString += '; secure';
}
cookieString += `; SameSite=${sameSite}`;
document.cookie = cookieString;
}
// Usage
setCookieAdvanced('auth_token', 'abc123', {
expires: new Date('2023-12-31'),
secure: true,
sameSite: 'Strict'
});
// Set multiple cookies at once
function setMultipleCookies(cookies) {
Object.entries(cookies).forEach(([name, value]) => {
setCookie(name, value, 30);
});
}
setMultipleCookies({
'theme': 'dark',
'language': 'en',
'timezone': 'UTC'
});
Reading Cookies
// Basic cookie reading
function getCookie(name) {
const nameEQ = name + '=';
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i];
while (cookie.charAt(0) === ' ') {
cookie = cookie.substring(1, cookie.length);
}
if (cookie.indexOf(nameEQ) === 0) {
return decodeURIComponent(cookie.substring(nameEQ.length, cookie.length));
}
}
return null;
}
// Usage
const username = getCookie('username');
const theme = getCookie('theme');
console.log('Username:', username);
console.log('Theme:', theme);
// Get all cookies as object
function getAllCookies() {
const cookies = {};
if (document.cookie) {
document.cookie.split(';').forEach(cookie => {
const [name, value] = cookie.trim().split('=');
if (name && value) {
cookies[decodeURIComponent(name)] = decodeURIComponent(value);
}
});
}
return cookies;
}
// Usage
const allCookies = getAllCookies();
console.log('All cookies:', allCookies);
console.log('User preferences:', {
theme: allCookies.theme,
language: allCookies.language
});
// Check if cookie exists
function hasCookie(name) {
return getCookie(name) !== null;
}
// Usage
if (hasCookie('user_session')) {
console.log('User is logged in');
} else {
console.log('User is not logged in');
}
Deleting Cookies
Methods for removing cookies from the browser.
Basic Deletion
// Delete cookie by setting expiration in the past
function deleteCookie(name) {
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
}
// Usage
deleteCookie('username');
deleteCookie('theme');
// Delete cookie with specific path
function deleteCookieWithPath(name, path) {
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=' + path;
}
// Delete cookie with specific domain
function deleteCookieWithDomain(name, domain) {
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=' + domain;
}
// Delete all cookies
function deleteAllCookies() {
const cookies = document.cookie.split(';');
cookies.forEach(cookie => {
const [name] = cookie.trim().split('=');
if (name) {
deleteCookie(decodeURIComponent(name));
}
});
}
// Usage
deleteAllCookies();
// Delete cookies matching pattern
function deleteCookiesByPattern(pattern) {
const cookies = getAllCookies();
Object.keys(cookies).forEach(name => {
if (pattern.test(name)) {
deleteCookie(name);
}
});
}
// Delete all analytics cookies
deleteCookiesByPattern(/^ga_/);
// Delete all temporary cookies
deleteCookiesByPattern(/^temp_/);
Advanced Deletion
// Delete cookie with all original attributes
function deleteCookieAdvanced(name, originalOptions = {}) {
const {
domain = null,
path = '/',
secure = false,
sameSite = 'Lax'
} = originalOptions;
let cookieString = `${encodeURIComponent(name)}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
if (domain) {
cookieString += `; domain=${domain}`;
}
cookieString += `; path=${path}`;
if (secure) {
cookieString += '; secure';
}
cookieString += `; SameSite=${sameSite}`;
document.cookie = cookieString;
}
// Store original cookie options when setting
const cookieOptions = new Map();
function setCookieWithOptions(name, value, options = {}) {
// Store options for later deletion
cookieOptions.set(name, options);
setCookieAdvanced(name, value, options);
}
function deleteCookieWithOptions(name) {
const options = cookieOptions.get(name) || {};
deleteCookieAdvanced(name, options);
}
// Usage
setCookieWithOptions('secure_token', 'abc123', {
domain: '.example.com',
path: '/api',
secure: true,
sameSite: 'Strict'
});
deleteCookieWithOptions('secure_token');
Cookie Security
Important security considerations when working with cookies.
Security Attributes
// Secure flag - HTTPS only
function setSecureCookie(name, value) {
document.cookie = `${name}=${value}; secure; path=/`;
}
// HttpOnly flag - Server-side only (cannot be set via JavaScript)
// This must be set by server HTTP header:
// Set-Cookie: sessionid=abc123; HttpOnly; Secure
// SameSite flag - CSRF protection
// Strict: Cookie only sent with same-site requests
setCookie('csrf_token', 'token', {
secure: true,
sameSite: 'Strict'
});
// Lax: Cookie sent with same-site requests and top-level navigation
setCookie('preferences', 'dark', {
sameSite: 'Lax'
});
// None: Cookie sent with all requests (requires Secure)
setCookie('tracking_id', '12345', {
secure: true,
sameSite: 'None'
});
// Content Security Policy for cookies
// Set in HTTP header:
// Content-Security-Policy: cookie-src 'self'; script-src 'self'
Data Protection
// Encode cookie values
function setSecureCookie(name, value, options = {}) {
// Always encode values
const encodedValue = encodeURIComponent(value);
const encodedName = encodeURIComponent(name);
let cookieString = `${encodedName}=${encodedValue}`;
// Add security attributes
if (options.secure !== false) {
cookieString += '; secure';
}
if (options.httpOnly) {
// Note: HttpOnly cannot be set via JavaScript
console.warn('HttpOnly cannot be set via JavaScript');
}
cookieString += `; SameSite=${options.sameSite || 'Strict'}`;
cookieString += `; path=${options.path || '/'}`;
if (options.expires) {
cookieString += `; expires=${options.expires.toUTCString()}`;
}
if (options.domain) {
cookieString += `; domain=${options.domain}`;
}
document.cookie = cookieString;
}
// Validate cookie data
function validateCookieData(data, schema) {
if (typeof data !== 'string') {
data = String(data);
}
// Sanitize data
const sanitized = data
.replace(/[<>]/g, '') // Remove potential HTML
.substring(0, 4000); // Limit length
// Validate against schema
if (schema) {
if (schema.maxLength && sanitized.length > schema.maxLength) {
throw new Error('Cookie data too long');
}
if (schema.pattern && !schema.pattern.test(sanitized)) {
throw new Error('Cookie data invalid format');
}
}
return sanitized;
}
// Usage with validation
try {
setSecureCookie('user_id', '123', {
secure: true,
sameSite: 'Strict'
});
} catch (error) {
console.error('Failed to set cookie:', error);
}
// Never store sensitive data in cookies
// BAD:
setCookie('password', 'user_password'); // NEVER DO THIS
setCookie('ssn', '123-45-6789'); // NEVER DO THIS
setCookie('api_key', 'secret_key_123'); // NEVER DO THIS
// GOOD:
setCookie('session_id', 'abc123', { secure: true, sameSite: 'Strict' });
setCookie('preferences', JSON.stringify({ theme: 'dark' }));
Cookie Management Class
Creating a comprehensive cookie management utility.
Cookie Manager Implementation
class CookieManager {
constructor(options = {}) {
this.defaults = {
expires: null,
maxAge: null,
domain: null,
path: '/',
secure: window.location.protocol === 'https:',
sameSite: 'Lax',
...options
};
}
set(name, value, options = {}) {
const config = { ...this.defaults, ...options };
// Validate and sanitize
if (typeof value !== 'string') {
value = JSON.stringify(value);
}
const sanitizedValue = this.sanitize(value);
const sanitizedName = this.sanitize(name);
let cookieString = `${encodeURIComponent(sanitizedName)}=${encodeURIComponent(sanitizedValue)}`;
if (config.expires) {
cookieString += `; expires=${config.expires.toUTCString()}`;
}
if (config.maxAge) {
cookieString += `; max-age=${config.maxAge}`;
}
if (config.domain) {
cookieString += `; domain=${config.domain}`;
}
cookieString += `; path=${config.path}`;
if (config.secure) {
cookieString += '; secure';
}
cookieString += `; SameSite=${config.sameSite}`;
document.cookie = cookieString;
// Store options for deletion
this._storeOptions(name, config);
return true;
}
get(name, defaultValue = null) {
const sanitizedName = this.sanitize(name);
const nameEQ = sanitizedName + '=';
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i];
while (cookie.charAt(0) === ' ') {
cookie = cookie.substring(1, cookie.length);
}
if (cookie.indexOf(nameEQ) === 0) {
const value = decodeURIComponent(cookie.substring(nameEQ.length, cookie.length));
// Try to parse as JSON
try {
return JSON.parse(value);
} catch {
return value;
}
}
}
return defaultValue;
}
remove(name) {
const sanitizedName = this.sanitize(name);
const options = this._getStoredOptions(name) || this.defaults;
let cookieString = `${encodeURIComponent(sanitizedName)}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
if (options.domain) {
cookieString += `; domain=${options.domain}`;
}
cookieString += `; path=${options.path}`;
if (options.secure) {
cookieString += '; secure';
}
cookieString += `; SameSite=${options.sameSite}`;
document.cookie = cookieString;
this._removeStoredOptions(name);
}
getAll() {
const cookies = {};
if (document.cookie) {
document.cookie.split(';').forEach(cookie => {
const [name, value] = cookie.trim().split('=');
if (name && value) {
const decodedName = decodeURIComponent(name);
const decodedValue = decodeURIComponent(value);
try {
cookies[decodedName] = JSON.parse(decodedValue);
} catch {
cookies[decodedName] = decodedValue;
}
}
});
}
return cookies;
}
exists(name) {
return this.get(name) !== null;
}
clear() {
const cookies = this.getAll();
Object.keys(cookies).forEach(name => this.remove(name));
}
// Private methods
sanitize(value) {
return String(value)
.replace(/[;<>\\"']/g, '') // Remove dangerous characters
.substring(0, 4000); // Limit length
}
_storeOptions(name, options) {
if (!this._options) this._options = new Map();
this._options.set(name, options);
}
_getStoredOptions(name) {
return this._options ? this._options.get(name) : null;
}
_removeStoredOptions(name) {
if (this._options) this._options.delete(name);
}
}
// Usage
const cookies = new CookieManager({
secure: true,
sameSite: 'Strict'
});
// Set cookies
cookies.set('user', { id: 123, name: 'John' });
cookies.set('preferences', { theme: 'dark', language: 'en' }, {
expires: new Date('2023-12-31')
});
// Get cookies
const user = cookies.get('user');
const theme = cookies.get('preferences', { theme: 'light' }).theme;
// Check existence
if (cookies.exists('session')) {
console.log('Session exists');
}
// Remove specific cookie
cookies.remove('user');
// Get all cookies
const allCookies = cookies.getAll();
console.log('All cookies:', allCookies);
Practical Use Cases
Common scenarios where cookies are the appropriate solution.
Session Management
// Session management with cookies
class SessionManager {
constructor() {
this.cookieManager = new CookieManager({
secure: true,
sameSite: 'Strict',
maxAge: 3600 // 1 hour
});
}
login(userData) {
const session = {
id: this.generateSessionId(),
userId: userData.id,
loginTime: new Date().toISOString(),
lastActivity: new Date().toISOString()
};
this.cookieManager.set('session', session);
// Set user preferences cookie
this.cookieManager.set('user_prefs', userData.preferences, {
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // 1 year
});
return session.id;
}
logout() {
this.cookieManager.remove('session');
// Optionally keep preferences
// this.cookieManager.remove('user_prefs');
}
isLoggedIn() {
const session = this.cookieManager.get('session');
return session && this.isValidSession(session);
}
getCurrentUser() {
const session = this.cookieManager.get('session');
return session ? session.userId : null;
}
isValidSession(session) {
if (!session) return false;
const now = new Date();
const lastActivity = new Date(session.lastActivity);
const maxAge = 60 * 60 * 1000; // 1 hour
return (now - lastActivity) < maxAge;
}
updateActivity() {
const session = this.cookieManager.get('session');
if (session) {
session.lastActivity = new Date().toISOString();
this.cookieManager.set('session', session);
}
}
generateSessionId() {
return 'sess_' + Math.random().toString(36).substr(2, 9);
}
}
// Usage
const session = new SessionManager();
// Login
session.login({
id: 123,
name: 'John Doe',
preferences: {
theme: 'dark',
language: 'en'
}
});
// Check login status
if (session.isLoggedIn()) {
console.log('User is logged in');
console.log('Current user:', session.getCurrentUser());
}
// Update activity on user interaction
document.addEventListener('click', () => {
session.updateActivity();
});
// Logout
session.logout();
User Preferences
// User preferences with cookies
class UserPreferences {
constructor() {
this.cookieManager = new CookieManager();
this.defaults = {
theme: 'light',
language: 'en',
fontSize: '16',
autoSave: true,
notifications: true
};
}
get(key) {
const prefs = this.cookieManager.get('preferences') || {};
return prefs[key] !== undefined ? prefs[key] : this.defaults[key];
}
set(key, value) {
let prefs = this.cookieManager.get('preferences') || {};
prefs[key] = value;
this.cookieManager.set('preferences', prefs, {
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // 1 year
});
this.applyPreference(key, value);
}
getAll() {
return { ...this.defaults, ...this.cookieManager.get('preferences') };
}
reset() {
this.cookieManager.set('preferences', this.defaults, {
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)
});
this.applyAllPreferences();
}
applyPreference(key, value) {
switch (key) {
case 'theme':
document.body.className = value;
break;
case 'language':
document.documentElement.lang = value;
break;
case 'fontSize':
document.body.style.fontSize = value + 'px';
break;
}
}
applyAllPreferences() {
const prefs = this.getAll();
Object.keys(prefs).forEach(key => {
this.applyPreference(key, prefs[key]);
});
}
}
// Usage
const prefs = new UserPreferences();
// Set preferences
prefs.set('theme', 'dark');
prefs.set('language', 'es');
prefs.set('fontSize', '18');
// Get preferences
console.log('Current theme:', prefs.get('theme'));
console.log('All preferences:', prefs.getAll());
// Reset to defaults
prefs.reset();
Browser Compatibility and Limitations
Understanding cookie limitations across different browsers.
Cookie Limitations
// Check cookie limitations
function getCookieLimitations() {
const test = 'test_cookie=' + 'x'.repeat(4000); // 4KB test
document.cookie = test;
const limitations = {
sizeLimit: null,
countLimit: null,
supported: document.cookie !== undefined
};
// Test size limit
if (document.cookie.includes(test)) {
limitations.sizeLimit = '4KB+';
} else {
// Try smaller sizes
for (let size = 4000; size >= 100; size -= 100) {
const smallerTest = 'test_cookie=' + 'x'.repeat(size);
document.cookie = smallerTest;
if (document.cookie.includes(smallerTest)) {
limitations.sizeLimit = `~${size} bytes`;
break;
}
}
}
// Test count limit
let cookieCount = 0;
const testCookies = [];
try {
for (let i = 0; i < 100; i++) { // Test up to 100 cookies
const name = `test_cookie_${i}`;
document.cookie = `${name}=value`;
if (document.cookie.includes(name)) {
cookieCount++;
testCookies.push(name);
} else {
break;
}
}
} catch (e) {
console.error('Error testing cookie count:', e);
}
// Clean up test cookies
testCookies.forEach(name => {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
});
limitations.countLimit = cookieCount;
return limitations;
}
// Browser-specific considerations
function getBrowserInfo() {
const info = {
name: '',
version: '',
mobile: false,
cookieSupport: true
};
// Detect browser
const ua = navigator.userAgent;
if (ua.includes('Chrome')) {
info.name = 'Chrome';
} else if (ua.includes('Firefox')) {
info.name = 'Firefox';
} else if (ua.includes('Safari') && !ua.includes('Chrome')) {
info.name = 'Safari';
} else if (ua.includes('Edge')) {
info.name = 'Edge';
}
// Mobile detection
info.mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua);
// Cookie support in private browsing
try {
document.cookie = 'test=1';
info.cookieSupport = document.cookie.includes('test=1');
document.cookie = 'test=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
} catch (e) {
info.cookieSupport = false;
}
return info;
}
// Usage
const limitations = getCookieLimitations();
console.log('Cookie limitations:', limitations);
const browser = getBrowserInfo();
console.log('Browser info:', browser);
if (!browser.cookieSupport) {
console.warn('Cookies are not supported (private browsing?)');
// Fallback to sessionStorage or localStorage
}
Privacy Mode Detection
// Detect private browsing mode
function isPrivateMode() {
// Safari
const isSafari = navigator.userAgent.includes('Safari') &&
!navigator.userAgent.includes('Chrome');
if (isSafari) {
try {
localStorage.setItem('test', '1');
localStorage.removeItem('test');
return false;
} catch (e) {
return true;
}
}
// Chrome/Edge
try {
const test = 'test';
window.indexedDB.open(test, 1).onupgraded = function(e) {
if (e.oldVersion === null) {
// DB didn't exist, likely private mode
return true;
}
};
} catch (e) {
return true;
}
return false;
}
// Handle private mode
function handlePrivateMode() {
if (isPrivateMode()) {
console.warn('Private browsing mode detected');
// Show message to user
const message = document.createElement('div');
message.textContent = 'Some features may not work in private browsing mode';
message.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: #ffeb3b;
color: #333;
padding: 10px 20px;
border-radius: 5px;
z-index: 9999;
`;
document.body.appendChild(message);
// Auto-remove after 5 seconds
setTimeout(() => {
if (message.parentNode) {
message.parentNode.removeChild(message);
}
}, 5000);
return true;
}
return false;
}
// Usage
if (handlePrivateMode()) {
// Fallback behavior for private mode
enableMemoryStorage();
}
Modern Alternatives
When to use localStorage, sessionStorage, or modern storage APIs instead of cookies.
Storage Comparison
// Storage API comparison
const storageComparison = {
cookies: {
capacity: '~4KB per cookie',
expiration: 'Flexible',
sentToServer: true,
access: 'Client + Server',
security: 'HttpOnly, Secure, SameSite',
useCase: 'Session management, server data'
},
localStorage: {
capacity: '~5-10MB',
expiration: 'Manual',
sentToServer: false,
access: 'Client only',
security: 'Same-origin policy',
useCase: 'Client-side preferences, cache'
},
sessionStorage: {
capacity: '~5-10MB',
expiration: 'Session end',
sentToServer: false,
access: 'Client only',
security: 'Same-origin policy',
useCase: 'Temporary session data'
}
};
// Choose appropriate storage
function chooseStorage(data, options = {}) {
const { needsServerAccess = false, persistent = true, sensitive = false } = options;
if (needsServerAccess) {
return 'cookies';
}
if (sensitive) {
return 'cookies'; // With proper security flags
}
if (persistent) {
return 'localStorage';
}
return 'sessionStorage';
}
// Universal storage manager
class UniversalStorage {
constructor() {
this.cookies = new CookieManager();
this.localStorage = window.localStorage;
this.sessionStorage = window.sessionStorage;
}
set(key, value, options = {}) {
const storageType = chooseStorage(value, options);
switch (storageType) {
case 'cookies':
return this.cookies.set(key, value, options);
case 'localStorage':
return this.localStorage.setItem(key, JSON.stringify(value));
case 'sessionStorage':
return this.sessionStorage.setItem(key, JSON.stringify(value));
}
}
get(key, defaultValue = null, options = {}) {
const storageType = chooseStorage(defaultValue, options);
let value;
switch (storageType) {
case 'cookies':
value = this.cookies.get(key, defaultValue);
break;
case 'localStorage':
value = JSON.parse(this.localStorage.getItem(key) || 'null');
break;
case 'sessionStorage':
value = JSON.parse(this.sessionStorage.getItem(key) || 'null');
break;
}
return value !== null ? value : defaultValue;
}
remove(key, options = {}) {
const storageType = chooseStorage(null, options);
switch (storageType) {
case 'cookies':
return this.cookies.remove(key);
case 'localStorage':
return this.localStorage.removeItem(key);
case 'sessionStorage':
return this.sessionStorage.removeItem(key);
}
}
}
// Usage
const storage = new UniversalStorage();
// Store user preferences (localStorage)
storage.set('preferences', { theme: 'dark' });
// Store session token (cookies)
storage.set('auth_token', 'abc123', {
needsServerAccess: true,
secure: true
});
// Store temporary data (sessionStorage)
storage.set('temp_form_data', { step: 2 }, {
persistent: false
});
Modern Storage APIs
// IndexedDB for large structured data
class IndexedDBStorage {
constructor(dbName, version = 1) {
this.dbName = dbName;
this.version = version;
this.db = null;
}
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create object store if it doesn't exist
if (!db.objectStoreNames.contains('data')) {
db.createObjectStore('data', { keyPath: 'id', autoIncrement: true });
}
};
});
}
async set(key, value) {
const transaction = this.db.transaction(['data'], 'readwrite');
const store = transaction.objectStore('data');
return new Promise((resolve, reject) => {
const request = store.put({ id: key, value, timestamp: Date.now() });
request.onsuccess = () => resolve(true);
request.onerror = () => reject(request.error);
});
}
async get(key) {
const transaction = this.db.transaction(['data'], 'readonly');
const store = transaction.objectStore('data');
return new Promise((resolve, reject) => {
const request = store.get(key);
request.onsuccess = () => resolve(request.result ? request.result.value : null);
request.onerror = () => reject(request.error);
});
}
async remove(key) {
const transaction = this.db.transaction(['data'], 'readwrite');
const store = transaction.objectStore('data');
return new Promise((resolve, reject) => {
const request = store.delete(key);
request.onsuccess = () => resolve(true);
request.onerror = () => reject(request.error);
});
}
}
// Usage
const idbStorage = new IndexedDBStorage('MyAppDB');
// Initialize and use
idbStorage.init().then(() => {
return idbStorage.set('user_data', { name: 'John', preferences: {} });
}).then(() => {
return idbStorage.get('user_data');
}).then(data => {
console.log('User data:', data);
});
Best Practices
Guidelines for using cookies effectively and securely.
Security Best Practices
// Always use secure flags for sensitive cookies
function setSecureCookie(name, value) {
const options = {
secure: window.location.protocol === 'https:',
sameSite: 'Strict',
httpOnly: false // Note: Can only be set server-side
};
// Set reasonable expiration
if (name.includes('session') || name.includes('token')) {
options.maxAge = 3600; // 1 hour
} else {
options.expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000); // 1 year
}
return new CookieManager().set(name, value, options);
}
// Validate and sanitize all cookie data
function sanitizeCookieValue(value) {
if (typeof value !== 'string') {
value = String(value);
}
// Remove potentially dangerous characters
return value
.replace(/[<>"'\\]/g, '') // Remove HTML/JS characters
.substring(0, 4000); // Limit length
}
// Implement proper session management
class SecureSessionManager {
constructor() {
this.cookieManager = new CookieManager({
secure: true,
sameSite: 'Strict'
});
}
createSession(userData) {
const sessionId = this.generateSecureSessionId();
const sessionData = {
id: sessionId,
userId: userData.id,
createdAt: new Date().toISOString(),
lastActivity: new Date().toISOString(),
userAgent: navigator.userAgent.substring(0, 200) // Limit user agent
};
this.cookieManager.set('session', sessionData, {
maxAge: 1800 // 30 minutes
});
return sessionId;
}
validateSession(session) {
if (!session || !session.id) return false;
const now = new Date();
const createdAt = new Date(session.createdAt);
const lastActivity = new Date(session.lastActivity);
const maxAge = 30 * 60 * 1000; // 30 minutes
// Check session age
if (now - createdAt > maxAge) {
return false;
}
// Check activity timeout
if (now - lastActivity > maxAge) {
return false;
}
return true;
}
generateSecureSessionId() {
// Use cryptographically secure random generator if available
if (window.crypto && window.crypto.getRandomValues) {
const array = new Uint8Array(16);
window.crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
// Fallback
return 'sess_' + Math.random().toString(36).substr(2, 16) + '_' + Date.now();
}
}
Performance and Compatibility
// Minimize cookie operations
class OptimizedCookieManager {
constructor() {
this.cache = new Map();
this.dirty = new Set();
}
get(name) {
if (this.cache.has(name)) {
return this.cache.get(name);
}
const value = this.getCookieFromDocument(name);
this.cache.set(name, value);
return value;
}
set(name, value, options = {}) {
this.cache.set(name, value);
this.dirty.add(name);
// Batch write operations
if (this.batchTimeout) {
clearTimeout(this.batchTimeout);
}
this.batchTimeout = setTimeout(() => {
this.flushDirty();
}, 100);
}
flushDirty() {
this.dirty.forEach(name => {
const value = this.cache.get(name);
this.setCookieInDocument(name, value);
});
this.dirty.clear();
}
// Browser compatibility checks
isSupported() {
return typeof document !== 'undefined' &&
typeof document.cookie !== 'undefined';
}
getMaxCookieSize() {
// Test actual cookie size limit
let maxSize = 0;
const testValue = 'x';
while (maxSize < 5000) { // Reasonable upper limit
const testCookie = `size_test=${testValue.repeat(maxSize)}`;
document.cookie = testCookie;
if (document.cookie.includes(testCookie)) {
maxSize += 100;
} else {
break;
}
document.cookie = 'size_test=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
}
return maxSize;
}
}
// Feature detection before usage
function initializeWithFallback() {
if (typeof document !== 'undefined' && document.cookie) {
// Use cookies
return new CookieManager();
} else if (typeof localStorage !== 'undefined') {
// Fallback to localStorage
console.warn('Cookies not available, using localStorage');
return new LocalStorageManager();
} else {
// Final fallback to memory
console.warn('No persistent storage available, using memory');
return new MemoryStorageManager();
}
}
// Usage
const storage = initializeWithFallback();
storage.set('preferences', { theme: 'dark' });
Summary
Key Takeaways
- Cookies are sent to server with each HTTP request
- Limited to ~4KB per cookie with domain-wide limits
- Support expiration dates and security attributes
- Essential for server-side session management
- SameSite attribute provides CSRF protection
Best Practices
- Use Secure flag for sensitive cookies over HTTPS
- Use SameSite=Strict for CSRF protection
- Always validate and sanitize cookie data
- Use appropriate storage (cookies vs localStorage vs sessionStorage)
- Implement proper session management with expiration
Common Pitfalls
- Storing sensitive data in plaintext cookies
- Not using security flags (Secure, HttpOnly, SameSite)
- Exceeding cookie size limits
- Not handling private browsing mode properly
- Using cookies when localStorage would be more appropriate