- Published on
Frontend Security 2025 — XSS, CSRF, CSP, Trusted Types, JWT, OAuth, PKCE, Passkeys, Supply Chain, SRI (S6 E9)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
Prologue — the browser became a runtime, and attackers noticed
Frontend security used to mean "sanitize user input and set secure cookies." In 2026 the threat surface is an entire runtime: service workers, extensions, third-party scripts, bundled dependencies, and your own source. A frontend engineer without a security baseline ships vulnerabilities.
This post is the practical playbook: the threats that actually matter, the defenses that actually work, and the minimum every team should have.
1. The 2026 threat model
The OWASP Top 10 didn't change much. What changed is where the risks live:
- Injection (mostly XSS) — still #1.
- Broken authentication — session hijacks, token theft.
- Sensitive data exposure — in bundles, logs, cached responses.
- Supply chain compromise — the npm/CDN you depend on.
- Misconfiguration — wrong CSP, open CORS, leaked env vars.
2. XSS — Cross-Site Scripting
Three types:
- Reflected — attacker crafts URL with script, victim clicks.
- Stored — script saved in DB, served to all users (most dangerous).
- DOM-based — client-side sink (
innerHTML,eval) reads untrusted source.
Mitigations (layered)
- Framework output encoding — React, Vue, Svelte escape by default.
dangerouslySetInnerHTML,v-html,{@html}are the exit hatches. - Sanitizer API (2024 baseline) — browser-native sanitization:
element.setHTML(untrustedHtml). - DOMPurify — still the fallback for older browsers.
- CSP v3 with
strict-dynamic— even if XSS lands, scripts can't execute without nonce/hash. - Trusted Types — TS-like runtime check that
innerHTML = xrequires aTrustedHTMLobject.
CSP starter (2026)
Content-Security-Policy:
default-src 'self';
script-src 'nonce-{nonce}' 'strict-dynamic';
style-src 'self' 'nonce-{nonce}';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'none';
object-src 'none';
require-trusted-types-for 'script';
trusted-types default dompurify;
strict-dynamic means "trust scripts with this nonce, and scripts they load" — removes the whitelist management nightmare.
3. CSRF — Cross-Site Request Forgery
For cookie-authenticated APIs, CSRF is still a risk.
Mitigations
SameSite=Lax(now the default in major browsers) kills most CSRF.SameSite=Strictwhere you can.- Double-submit tokens — set a CSRF token in a cookie; require it as a header.
- Origin/Referer validation at the server.
- Require
fetchwith credentials explicit — don't defaultcredentials: include.
For token-based APIs (JWT in Authorization header), CSRF doesn't apply — but XSS exfil does.
4. Authentication — OAuth, PKCE, Passkeys
OAuth 2.0 + PKCE
SPAs and mobile apps must use PKCE (Proof Key for Code Exchange). Implicit flow is deprecated.
Client → /authorize?code_challenge=S256(random)
IdP → redirects back with code
Client → /token with code + code_verifier
Tokens
- Store access tokens in memory where possible.
- Refresh tokens in httpOnly, Secure, SameSite=Strict cookie.
- Short access token lifetime (5–15 min); refresh rotates.
Passkeys (WebAuthn)
2024 was the passkey breakthrough — Apple, Google, Microsoft all cross-synced. For new apps, passkey-first, password-fallback is now the modern default.
navigator.credentials.create({ publicKey: ... })to register.navigator.credentials.get({ publicKey: ... })to sign in.- Phishing-resistant by design (domain-bound).
5. Cookies and sessions
Secure— only sent over HTTPS.HttpOnly— not readable from JS.SameSite=Lax(default) orStrict.Path=/or narrower.Max-AgeorExpires— prefer Max-Age.- Name-prefix
__Host-for strongest security (__Host-session=...).
6. JWT pitfalls
JWT is fine when used correctly — but common mistakes:
- Using JWT where sessions would do. If you can't revoke a JWT until it expires, you have a security problem.
- Storing JWT in localStorage — XSS steals it. Use httpOnly cookies.
- Weak secrets — HS256 with short secret is cracked in minutes. Use asymmetric (RS256/ES256) with rotation.
- Not validating
audandiss— easy to mix tokens between services. alg: none— ancient library bug, still worth checking.
7. Third-party scripts and supply chain
Every <script src=...> is a trust relationship. 2024 attacks (polyfill.io, node-ipc earlier) showed the risk.
Mitigations
- Subresource Integrity (SRI) —
<script src=... integrity="sha384-..." crossorigin="anonymous">. - Avoid bundling untrusted CDN scripts — self-host where possible.
- Audit dependency changes (Dependabot, Renovate, Snyk).
- npm provenance — verify package origin.
- SBOM + SLSA attestation (see S6 E12).
- Lockfile integrity —
npm ci,pnpm install --frozen-lockfile.
8. CORS — the most-misunderstood header
CORS isn't security. It's a browser policy that protects users from malicious pages making authenticated requests to third-party APIs. Server still validates every request.
Rules
- Don't echo arbitrary
Originheader inAccess-Control-Allow-Origin. - Pair
Allow-Credentials: truewith specific origin, never*. - Prefer no CORS (same-origin) when possible.
9. Click-jacking and framing
X-Frame-Options: DENYorSAMEORIGIN(legacy but widely supported).- CSP
frame-ancestors 'none'(modern, replaces X-Frame-Options). - For embeddable widgets (like Stripe Checkout), allowlist specific origins.
10. Headers cheat sheet (2026)
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Content-Security-Policy: (see section 2)
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), geolocation=(), microphone=()
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-site
Tools: securityheaders.com, Mozilla Observatory, browser DevTools Security panel.
11. Common leaks
- API keys in client bundles.
NEXT_PUBLIC_*is exposed — never put secrets there. - Source maps in production with full source — consider
hidden-source-mapwith uploads to Sentry only. - Console logs with PII — strip in production.
- Error messages leaking stack traces — sanitize for users, full for logs.
- Cache-control on authenticated pages — use
Cache-Control: privateorno-store. localStoragefor sensitive data — XSS steal. UsesessionStorageat most, httpOnly cookies preferred.- URLs with tokens — leak into Referer headers, analytics, logs.
12. Security in the AI era
- Prompt injection in LLM apps — treat user content before LLM as untrusted input. Sanitize prompts, separate system and user.
- Tool use access — if the LLM can call tools, restrict what it can do (allowlist).
- AI coding assistants leaking secrets —
.envin context = leak risk. - Hallucinated packages — LLMs sometimes recommend non-existent npm packages, which attackers then register.
13. The minimum every team needs
- CSP v3 with nonces on HTML pages (not just
unsafe-inline). Secure,HttpOnly,SameSiteon session cookies.- HSTS preload.
- Subresource Integrity on all third-party scripts.
- Dependabot/Renovate + audit workflow.
- No secrets in client bundles.
- CORS configured explicitly (not
*). - Regular dependency audit (
npm audit, Snyk). - Error tracking (Sentry) — so you see attacks early.
- Security review on any auth change.
12-item checklist
- CSP v3 with
strict-dynamicand nonces? - HSTS preload enabled?
- SRI on every third-party script?
- Passkey option available for auth?
- Short-lived access tokens + refresh rotation?
- XSS-resistant framework defaults used (no
dangerouslySetInnerHTML)? SameSite=Lax(or Strict) on auth cookies?- Source maps protected in production?
- Dependency audit automated?
- Security headers tool returns A+?
- Error tracking captures CSP violations?
- Secrets management audited (no
NEXT_PUBLIC_leakage)?
10 anti-patterns
dangerouslySetInnerHTMLwith user content.- JWT in
localStorage. Access-Control-Allow-Origin: *with credentials.- Ignoring CSP violation reports.
- Re-using JWT secrets across environments.
- Deploying source maps with full source.
- Passing auth tokens in URL query strings.
- Logging JWTs or cookies in client code.
- Bundling random CDN scripts without SRI.
- Rolling your own crypto (or JWT implementation).
Next episode
Season 6 Episode 10: State Management Renaissance 2025 — Zustand, Jotai, Valtio, TanStack Query, Signals, XState, RSC. Which state lives where in a 2026 React app.
— End of Frontend Security.