Angular Security Best Practices: XSS, Tokens, CSRF and Headers

Angular Tutorial: Beginner to Advanced · 18. Quality, security and delivery · 5 min read · Updated

Angular helps you stay safe by default, but it cannot protect you from unsafe choices. This lesson covers the main risks in a browser app and what to do about them. Remember the golden rule: everything that runs in the browser can be read and changed by the user, so real security always lives on the server.

What you will learn

  • How Angular's built-in protection against XSS works, and how people break it
  • How to treat tokens, cookies and CSRF
  • Headers and habits that reduce risk

1. Cross-site scripting (XSS)

XSS means an attacker gets their JavaScript to run on your page, for example through a comment field. Angular treats every value as untrusted and escapes it:

<!-- Safe: Angular escapes the text, so a <script> in userComment is shown as text -->
<p>{{ userComment }}</p>

<!-- Sanitised: dangerous parts such as script tags and event handlers are removed -->
<div [innerHTML]="userHtml"></div>

You can switch the protection off. Never do it with user data:

// File: src/app/security/unsafe-example.ts
import { Component, inject } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({
  selector: 'app-unsafe-example',
  template: `<div [innerHTML]="html"></div>`,
})
export class UnsafeExample {
  private readonly sanitizer = inject(DomSanitizer);

  // DANGEROUS if the string can come from a user or an API you do not control.
  protected html: SafeHtml = this.sanitizer.bypassSecurityTrustHtml('<b>trusted by you</b>');
}

Rules:

  • Avoid bypassSecurityTrust... methods. If a reviewer sees one, it must have a written reason.
  • Do not build HTML strings and assign them to innerHTML, outerHTML or document.write.
  • Do not use ElementRef.nativeElement.innerHTML. Bind with templates instead.
  • Never put user input into [href] or [src] without checking. Angular blocks javascript: URLs, which is one more reason not to bypass it.
  • If you must show rich text (for example blog content), sanitise it on the server with a maintained library, store a safe format such as Markdown or a block structure, and keep Angular's sanitiser on.

2. Tokens and cookies

Where to keep a login token is a trade-off:

  • `localStorage` / `sessionStorage`: Risk: Readable by any script on the page, so one XSS bug leaks it; Notes: Common, simple, and the least safe
  • In-memory variable: Risk: Lost on refresh, hard to steal with XSS after the fact; Notes: Combine with a refresh cookie
  • **HttpOnly, Secure, SameSite cookie**: Risk: JavaScript cannot read it, so XSS cannot steal it directly; Notes: Needs CSRF protection

For most apps, the safest setup is a session or refresh token in an HttpOnly, Secure, SameSite=Lax (or Strict) cookie set by your server, with short-lived access tokens kept in memory. Whatever you choose, use HTTPS everywhere, short lifetimes, and make logout revoke the session on the server.

3. CSRF (cross-site request forgery)

If you use cookies for auth, a malicious site can make the browser send requests to your API with those cookies. Defend with:

  • SameSite cookies,
  • a CSRF token the server checks on every state-changing request (POST, PUT, PATCH, DELETE).

Angular's HttpClient has built-in support for the common double-submit pattern: it reads a cookie named XSRF-TOKEN and sends its value in an X-XSRF-TOKEN header for mutating requests to the same origin. You can configure it:

// File: src/app/app.config.ts (excerpt)
import { provideHttpClient, withFetch, withXsrfConfiguration } from '@angular/common/http';

// providers: [
//   provideHttpClient(
//     withFetch(),
//     withXsrfConfiguration({ cookieName: 'XSRF-TOKEN', headerName: 'X-XSRF-TOKEN' }),
//   ),
// ]

The server must set the cookie and verify the header. If you authenticate with an Authorization: Bearer header instead of cookies, classic CSRF does not apply, but XSS protection matters even more.

4. Never trust the client

  • Guards and hidden buttons are usability, not security. Check permissions on every API call on the server.
  • Validate and sanitise all input on the server, even if the Angular form already validated it.
  • Do not put secrets in Angular code or environment files. Everything shipped to the browser is public. API keys that must stay secret belong on the server.
  • Do not return more data than the user may see and hope the UI hides it.

5. Content Security Policy (CSP) and other headers

A CSP tells the browser which scripts it may run, limiting the damage of an XSS bug. Set headers on your web server or CDN:

add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Start in report-only mode (Content-Security-Policy-Report-Only) to see what would break. The Angular build can also produce a strict, hash-based CSP (the security.autoCsp option, currently marked experimental), so check the current documentation before relying on it. Use frame-ancestors 'none' to prevent clickjacking.

6. Dependencies and build

  • Run npm audit and keep Angular and packages updated. ng update handles Angular upgrades.
  • Commit lock files, use npm ci in CI, and review new dependencies.
  • Do not paste unknown scripts into index.html. Third-party scripts have the same power as your own code.
  • Turn on source-map restrictions in production if you do not want to expose original code (it is not a secret either way).

7. Security checklist

  1. No bypassSecurityTrust... with user data.
  2. No innerHTML built by hand.
  3. HTTPS and security headers in production.
  4. Tokens in HttpOnly cookies or memory, short lifetimes.
  5. CSRF protection if you use cookies.
  6. Server-side authorisation and validation on every request.
  7. No secrets in the client bundle.
  8. Dependencies updated and audited.

Try it yourself

  1. Search your project for bypassSecurityTrust and innerHTML. Justify or remove each.
  2. Check your site's headers with your browser's Network tab, then add the missing ones.
  3. Write down where your app stores its token and what happens if an XSS bug appears.

Quick check

  1. Why is localStorage risky for tokens?
  2. When do you need CSRF protection?
  3. Why are route guards not security?

Next, automate quality checks with CI/CD. The official security guide is required reading.

Frequently asked questions

Is Angular secure by default?

It is safe against XSS in templates by default, but you can still create holes by bypassing sanitisation, trusting client checks, leaking tokens or shipping secrets.

Should I implement login myself?

Prefer a proven identity provider or a well-reviewed library. Password storage, sessions, resets and multi-factor login are easy to get wrong.