A trading application handles money. That makes it a target. The frontend is the most exposed surface of the stack - it runs on hardware you do not control, in a browser you do not control, on a network you do not control. Every security decision in the frontend has to account for this reality.
This is not a comprehensive security guide. It covers the frontend-specific concerns that are most relevant to trading applications and most commonly overlooked.
Content Security Policy
A strict Content Security Policy (CSP) is your first line of defence against XSS attacks. For a trading application, the CSP should be as restrictive as possible:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://your-cdn.com; connect-src 'self' wss://your-api.com https://your-api.com; font-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self';
Key points for trading apps:
connect-srcmust include your WebSocket endpoints (wss://). Forgetting this breaks your data feed.script-src 'self'without'unsafe-eval'or'unsafe-inline'prevents most XSS vectors. If your charting library requireseval(some do), consider switching libraries or using a hash-based exception.frame-src 'none'prevents clickjacking, where an attacker overlays your trading UI in an iframe to capture order submissions.
Deploy CSP in report-only mode first to catch violations before enforcing.
Embedding third-party widgets complicates this immediately. Our candlestick chart uses TradingView's AdvancedRealTimeChart, which renders inside an iframe - so frame-src cannot be 'none'. Every embedded widget is a hole in your CSP; the pragmatic response is to scope the exception as tightly as possible and accept the trade-off rather than pretending the rule is absolute.
Token storage
Trading applications authenticate users and store session tokens. Where you store those tokens determines your vulnerability surface.
The options
HttpOnly cookies - The browser sends them automatically. JavaScript cannot access them. This is the most secure option for session tokens.
localStorage - Accessible to any JavaScript on the page. An XSS vulnerability exposes the token. Not recommended for trading applications.
sessionStorage - Same XSS risk as localStorage, but tokens are cleared when the tab closes. Marginally better, still not ideal.
In-memory (JavaScript variable) - Not accessible to XSS unless the attacker can execute code in your application's scope (which CSP should prevent). Lost on page refresh, so the user re-authenticates more often.
For a trading application, use HttpOnly cookies for the session token:
// Server sets the cookie res.cookie("session", token, { httpOnly: true, secure: true, sameSite: "strict", maxAge: 3600000, path: "/", });
If you need a token accessible to JavaScript (for WebSocket authentication, for example), use a short-lived token stored in memory and refreshed via the HttpOnly session cookie:
async function getWsToken(): Promise<string> { const response = await fetch("/api/auth/ws-token", { credentials: "include", // Sends the HttpOnly cookie }); const { token } = await response.json(); return token; // Short-lived, stored only in memory }
WebSocket authentication
WebSocket connections do not send cookies on the upgrade request in all browsers. Authenticate WebSocket connections explicitly:
const wsToken = await getWsToken(); const ws = new WebSocket(`wss://api.example.com/stream?token=${wsToken}`);
Or authenticate after connection:
const ws = new WebSocket("wss://api.example.com/stream"); ws.onopen = () => { ws.send( JSON.stringify({ type: "auth", token: wsToken, }) ); };
The token should be short-lived (minutes, not hours) and scoped to WebSocket access only. If it is intercepted, the blast radius is limited to read-only market data, not account operations.
Protecting order submission
Order placement is the highest-risk action in a trading frontend. Protect it with multiple layers:
CSRF protection
If using cookie-based authentication, every state-changing request needs CSRF protection. Use a synchroniser token or the double-submit cookie pattern:
async function placeOrder(order: OrderRequest) { const csrfToken = getCsrfToken(); // From a meta tag or cookie return fetch("/api/orders", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken, }, credentials: "include", body: JSON.stringify(order), }); }
Rate limiting
Implement client-side rate limiting as a UX guard, but always enforce it server-side:
class OrderRateLimiter { private timestamps: number[] = []; private maxPerSecond: number; constructor(maxPerSecond: number = 5) { this.maxPerSecond = maxPerSecond; } canSubmit(): boolean { const now = Date.now(); this.timestamps = this.timestamps.filter((t) => now - t < 1000); return this.timestamps.length < this.maxPerSecond; } record() { this.timestamps.push(Date.now()); } }
Order confirmation
For orders above a configurable threshold, show a confirmation step. This prevents fat-finger errors and gives the trader a moment to verify:
function OrderConfirmation({ order, onConfirm, onCancel, }: OrderConfirmationProps) { return ( <div className="rounded-lg border p-4"> <p className="text-sm font-medium">Confirm Order</p> <div className="mt-2 space-y-1 text-sm"> <p> {order.side} {order.quantity} {order.instrument} </p> <p>@ {formatPrice(order.price)}</p> <p>Total: {formatCurrency(order.quantity * order.price)}</p> </div> <div className="mt-4 flex gap-2"> <Button onClick={onConfirm}>Confirm</Button> <Button variant="outline" onClick={onCancel}> Cancel </Button> </div> </div> ); }
Anti-phishing measures
Crypto trading platforms are frequent phishing targets. Help users verify they are on the real site:
Anti-phishing code
Let users set a personal anti-phishing code that appears on login and email communications. If the code is missing, they know the page is fake:
function LoginForm({ antiPhishingCode }: { antiPhishingCode: string | null }) { return ( <form> {antiPhishingCode && ( <div className="mb-4 rounded border border-primary/20 bg-primary/5 p-3"> <p className="text-xs text-muted-foreground"> Your anti-phishing code: </p> <p className="font-mono text-sm font-medium">{antiPhishingCode}</p> </div> )} {/* Login fields */} </form> ); }
Bookmark encouragement
Prompt users to bookmark the trading page rather than typing the URL or clicking links in emails. A simple banner on first login goes a long way.
Sensitive data in the browser
Console logging
Never log sensitive data (balances, API keys, order details) to the browser console in production:
// Development only if (process.env.NODE_ENV === "development") { console.log("Order submitted:", order); }
Users share screenshots of their console for debugging. Sensitive data in those screenshots is a liability.
Clipboard access
When users copy API keys or wallet addresses, clear the clipboard after a timeout:
async function copyWithTimeout(text: string, timeoutMs: number = 30000) { await navigator.clipboard.writeText(text); setTimeout(async () => { const current = await navigator.clipboard.readText(); if (current === text) { await navigator.clipboard.writeText(""); } }, timeoutMs); }
Screen capture protection
You cannot prevent screenshots, but you can make it harder for malware to scrape data. Use CSS to hide sensitive fields from screen capture APIs where supported:
.sensitive-data { content-visibility: auto; }
This is not a strong protection, but it adds friction for automated scraping.
Dependency supply chain
Trading frontends pull in dozens of npm packages. Each one is a potential attack vector. Mitigate this by:
- Locking dependency versions - Use exact versions in
package.jsonand commit the lockfile - Auditing regularly - Run
npm auditin CI and fail the build on high-severity vulnerabilities - Minimising dependencies - Every package you add is code you trust to run with access to your users' financial data
- Subresource integrity - For any scripts loaded from CDNs, use SRI hashes
Security headers checklist
Beyond CSP, set these headers on every response:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload X-Content-Type-Options: nosniff X-Frame-Options: DENY Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: camera=(), microphone=(), geolocation=()
These are low-effort, high-impact protections that should be standard on any trading platform.
The mindset
Frontend security in trading applications is not about building an impenetrable fortress. It is about layering defences so that no single failure gives an attacker access to user funds or data. CSP prevents XSS. HttpOnly cookies protect tokens. CSRF tokens guard mutations. Rate limits prevent abuse. Each layer catches what the others miss. Skip any one of them and you are relying on the remaining layers to be perfect, which they will not be.
