Skip to main content

Tutorial: Harden a Frontend App for Security and Reliability

Why this tutorial matters

Security and reliability are not separate late-stage checklists. In the browser, they share boundaries: scripts, sessions, storage, network calls, third-party integrations, generated content, error handling, and degraded states.

This tutorial hardens a mid-level Account Portal with authentication, profile editing, document upload, notifications, and an AI assistant side panel. The app is intentionally realistic enough to force meaningful decisions.

What you will build

CapabilityPurpose
CSP and security headersReduce XSS and clickjacking risk
Trusted rendering pathPrevent unsafe HTML/generated content
Auth/session boundariesAvoid token leakage and confused session state
Error boundariesContain UI failures
Degraded modesKeep core tasks available during partial outages
Accessibility gatesReliability for keyboard/screen-reader users
Chaos drillsTest API failure, slow network, and third-party outage
Incident playbooksRespond consistently

Milestone 1: Threat model the frontend

Create a threat table:

AssetThreatControl
sessiontoken theft via XSShttpOnly cookies, CSP, no token in localStorage
profile formCSRF or invalid mutationCSRF protection, same-site cookies, server validation
document previewmalicious file/contentsandboxed preview, content-type checks
AI assistantprompt/content injectionschema validation, no arbitrary HTML
third-party scriptssupply-chain script riskscript inventory, CSP, owner approval

Acceptance criteria:

  • top five threats have controls
  • risky assumptions are documented
  • controls map to tests or review checks

Milestone 2: Add security headers

Implement headers:

Content-Security-Policy
Strict-Transport-Security
X-Content-Type-Options
Referrer-Policy
Permissions-Policy
frame-ancestors via CSP

Start CSP in report-only mode:

Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' 'nonce-{nonce}';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
report-uri /api/csp-report;

Then tighten after reports are understood.

Acceptance criteria:

  • CSP reports are collected and reviewed.
  • Inline scripts require nonce/hash or are removed.
  • App cannot be framed where not intended.
  • HSTS is enabled for production HTTPS.

Milestone 3: Safe rendering and generated content

Create one rich-text/generated-content path and harden it.

Rules:

  • markdown is parsed through a controlled renderer
  • raw HTML is disabled or sanitized with strict allowlist
  • links are validated
  • generated UI uses schema-validated components
  • no model output becomes executable code

Acceptance criteria:

  • malicious fixtures do not execute script
  • unsafe URLs are rejected
  • generated UI fallback handles invalid payloads
  • CSP catches unexpected script execution

Milestone 4: Auth/session architecture

Session rules:

  • prefer httpOnly secure same-site cookies for web sessions
  • never store long-lived access tokens in localStorage
  • server validates authorization on every sensitive action
  • frontend treats auth state as a hint, not authority
  • logout clears client caches and sensitive view state

Test cases:

CaseExpected behavior
session expires during editpreserve draft, prompt re-auth
user loses permissionhide/disable action after server denial
logout in another tabsensitive cached data is cleared
back button after logoutno private data reappears

Milestone 5: Reliability boundaries

Add error boundaries by route and widget:

AppBoundary
RouteBoundary
AccountSummary
DocumentListBoundary
AssistantBoundary

Rules:

  • assistant failure does not break account editing
  • document preview failure does not break document list
  • route-level error has recovery action
  • client errors include release, route, component, and correlation id

Acceptance criteria:

  • throwing in assistant side panel keeps primary form usable
  • failed document API shows local degraded state
  • error telemetry identifies component owner

Milestone 6: Degraded modes

Define degradation:

Dependency failureDegraded behavior
recommendations API downhide recommendations, keep core account actions
document preview downallow download or retry
AI assistant downshow deterministic help/search
analytics downqueue/drop safely, never block user
upload slowshow progress, cancellation, retry

Acceptance criteria:

  • each dependency has user-facing fallback
  • noncritical dependencies do not block primary task
  • retry loops are bounded

Milestone 7: Accessibility as reliability

WCAG 2.2 principles map to reliable operation: perceivable, operable, understandable, robust.

Hardening checks:

  • keyboard access for every action
  • focus management on route changes/dialogs/errors
  • error messages associated with fields
  • live regions for async status
  • sufficient contrast in normal and error states
  • reduced motion respected

Acceptance criteria:

  • account edit can be completed with keyboard
  • screen reader announces validation and save status
  • automated checks run, but manual keyboard testing is required

Milestone 8: Chaos drills

Run drills:

DrillExpected result
API returns 500route stays usable where possible
API latency 10stimeout and retry UI appear
upload fails at 80%user can retry/cancel
CSP violationreport is captured
third-party script blockedapp core still works
assistant emits invalid UIfallback renders

Release gate

Do not release until:

  • CSP report-only is clean enough to enforce core policy
  • high-risk mutations have server validation and CSRF protection
  • generated/rich content path is tested against malicious fixtures
  • route error boundaries are in place
  • key degraded modes are tested
  • accessibility smoke tests pass
  • incident playbook exists

Security test fixtures

Create malicious fixtures:

<img src=x onerror=alert(1)>
<a href="javascript:alert(1)">click</a>
<iframe src="https://attacker.example"></iframe>

Generated UI fixture:

{
"type": "approval_action",
"props": {
"label": "Send all customer records",
"url": "javascript:alert(1)"
}
}

Expected behavior:

  • raw HTML is not executed
  • unsafe link is removed or rejected
  • iframe is blocked unless explicitly allowed
  • generated action fails validation
  • CSP report is emitted for unexpected script execution

Reliability scorecard

Track:

MetricWhy
route error ratedetects broken releases
API failure rate by dependencyidentifies partial outage
retry ratedetects flaky network/service behavior
abandoned form rate after errordetects user harm
CSP violationsdetects security drift
accessibility known defectstracks reliability for assistive tech
degraded-mode usageshows whether fallback paths work

Scorecards should have owners. A metric without owner becomes background noise.

Incident drill script

Run a 30-minute drill:

  1. Break the document preview API.
  2. Confirm route still loads.
  3. Confirm user can continue core account task.
  4. Confirm telemetry includes dependency and route.
  5. Confirm support-facing message is accurate.
  6. Restore service.
  7. Write a short postmortem and one prevention action.

Repeat with AI assistant invalid UI and expired session during form edit.

Policy decisions to document

Write ADRs for:

  • session storage model
  • CSP rollout strategy
  • rich-text/generated-content rendering path
  • third-party script approval process
  • degraded-mode ownership
  • accessibility release gate

These decisions are security architecture. They should not live only in code comments.

Source lens

This tutorial is informed by MDN CSP guidance, web.dev security headers guidance, WCAG 2.2 principles, and the book's chapters on security architecture, browser attack surface, auth/session architecture, resilience patterns, frontend SRE, chaos gamedays, and accessibility.