cookie.service.ts
angular/comprehensive-demo/src/app/core/services/storage/cookie.service.ts
/**
* Cookie Service
*
* This service provides methods to manage browser cookies.
*
* Cookies vs LocalStorage/SessionStorage:
* - Cookies are sent with every HTTP request (including to server)
* - Cookies have size limit (~4KB) vs ~5-10MB for storage APIs
* - Cookies can be set with expiration dates
* - Cookies can be restricted to specific paths and domains
* - Cookies support HttpOnly and Secure flags for security
*
* Use Cases:
* - Authentication tokens (though HttpOnly cookies are more secure)
* - User preferences that need to be sent to server
* - Tracking/analytics (with user consent)
* - CSRF tokens
* - Language/locale preferences
*
* Security Considerations:
* - Never store sensitive data in cookies without encryption
* - Use HttpOnly flag to prevent JavaScript access (XSS protection)
* - Use Secure flag for HTTPS-only transmission
* - Use SameSite attribute to prevent CSRF attacks
*/
import { Injectable } from '@angular/core';
import { environment } from '../../../../environments/environment';
/**
* Cookie Options Interface
*
* Defines options for setting cookies
*/
export interface CookieOptions {
/**
* Expiration date/time for the cookie
* If not set, cookie expires when browser closes (session cookie)
*/
expires?: Date | number; // Date object or days from now
/**
* Path where cookie is available
* Default: '/' (entire domain)
*/
path?: string;
/**
* Domain where cookie is available
* Default: current domain
*/
domain?: string;
/**
* If true, cookie is only sent over HTTPS
* Important for security in production
*/
secure?: boolean;
/**
* If true, cookie is not accessible via JavaScript (HttpOnly)
* Note: This can only be set server-side, not from JavaScript
*/
httpOnly?: boolean;
/**
* SameSite attribute to prevent CSRF attacks
* 'strict': Cookie not sent on cross-site requests
* 'lax': Cookie sent on top-level navigation (default in modern browsers)
* 'none': Cookie sent on all requests (requires Secure flag)
*/
sameSite?: 'strict' | 'lax' | 'none';
}
@Injectable({
providedIn: 'root'
})
export class CookieService {
private readonly prefix: string = environment.storage.cookiePrefix;
/**
* Set a cookie
*
* @param key - Cookie name (without prefix)
* @param value - Cookie value
* @param options - Cookie options (expiration, path, etc.)
*
* @example
* // Set a cookie that expires in 7 days
* this.cookieService.set('user_pref', 'dark_mode', { expires: 7 });
*
* @example
* // Set a secure cookie
* this.cookieService.set('token', 'abc123', { secure: true, sameSite: 'strict' });
*/
set(key: string, value: string, options: CookieOptions = {}): void {
const prefixedKey = `${this.prefix}${key}`;
let cookieString = `${this.encode(prefixedKey)}=${this.encode(value)}`;
// Handle expiration
if (options.expires) {
let expiresDate: Date;
if (typeof options.expires === 'number') {
// If number, treat as days from now
expiresDate = new Date();
expiresDate.setTime(expiresDate.getTime() + (options.expires * 24 * 60 * 60 * 1000));
} else {
expiresDate = options.expires;
}
cookieString += `; expires=${expiresDate.toUTCString()}`;
}
// Handle path
if (options.path) {
cookieString += `; path=${options.path}`;
} else {
cookieString += `; path=/`;
}
// Handle domain
if (options.domain) {
cookieString += `; domain=${options.domain}`;
}
// Handle secure flag
if (options.secure) {
cookieString += `; secure`;
}
// Handle SameSite
if (options.sameSite) {
cookieString += `; samesite=${options.sameSite}`;
}
// Note: httpOnly cannot be set from JavaScript for security reasons
// It must be set server-side via Set-Cookie header
document.cookie = cookieString;
}
/**
* Get a cookie value
*
* @param key - Cookie name (without prefix)
* @returns Cookie value or null if not found
*
* @example
* const theme = this.cookieService.get('theme'); // Returns 'dark' or null
*/
get(key: string): string | null {
const prefixedKey = `${this.prefix}${key}`;
const nameEQ = `${this.encode(prefixedKey)}=`;
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 this.decode(cookie.substring(nameEQ.length, cookie.length));
}
}
return null;
}
/**
* Remove a cookie
*
* This is done by setting the cookie with an expiration date in the past
*
* @param key - Cookie name (without prefix)
* @param options - Options (path, domain) must match original cookie
*
* @example
* this.cookieService.remove('user_pref');
*/
remove(key: string, options: Pick<CookieOptions, 'path' | 'domain'> = {}): void {
const prefixedKey = `${this.prefix}${key}`;
let cookieString = `${this.encode(prefixedKey)}=; expires=Thu, 01 Jan 1970 00:00:00 UTC`;
if (options.path) {
cookieString += `; path=${options.path}`;
} else {
cookieString += `; path=/`;
}
if (options.domain) {
cookieString += `; domain=${options.domain}`;
}
document.cookie = cookieString;
}
/**
* Check if a cookie exists
*
* @param key - Cookie name (without prefix)
* @returns true if cookie exists, false otherwise
*/
has(key: string): boolean {
return this.get(key) !== null;
}
/**
* Get all cookies as an object
*
* @returns Object with cookie names as keys and values as values
*/
getAll(): { [key: string]: string } {
const cookies: { [key: string]: string } = {};
const cookieArray = document.cookie.split(';');
for (let i = 0; i < cookieArray.length; i++) {
const cookie = cookieArray[i].trim();
if (cookie) {
const [name, value] = cookie.split('=');
const decodedName = this.decode(name);
// Only include cookies with our prefix
if (decodedName.startsWith(this.prefix)) {
const key = decodedName.substring(this.prefix.length);
cookies[key] = this.decode(value);
}
}
}
return cookies;
}
/**
* Encode a cookie value to handle special characters
*
* @param value - Value to encode
* @returns Encoded value
*/
private encode(value: string): string {
return encodeURIComponent(value);
}
/**
* Decode a cookie value
*
* @param value - Value to decode
* @returns Decoded value
*/
private decode(value: string): string {
return decodeURIComponent(value);
}
}
Related articles
server.js
server.js — javascript source code from the angular learning materials (angular/comprehensive-demo/server/server.js).
Read article →app.component.ts
app.component.ts — typescript source code from the angular learning materials (angular/comprehensive-demo/src/app/app.component.ts).
Read article →app.config.ts
app.config.ts — typescript source code from the angular learning materials (angular/comprehensive-demo/src/app/app.config.ts).
Read article →app.routes.ts
app.routes.ts — typescript source code from the angular learning materials (angular/comprehensive-demo/src/app/app.routes.ts).
Read article →auth.guard.ts
auth.guard.ts — typescript source code from the angular learning materials (angular/comprehensive-demo/src/app/core/guards/auth.guard.ts).
Read article →role.guard.ts
role.guard.ts — typescript source code from the angular learning materials (angular/comprehensive-demo/src/app/core/guards/role.guard.ts).
Read article →