Skip to content
Angular best practices 4 min read

Security Best Practices

Security in a single-page application is a shared responsibility between your code, the framework, and the browser. Angular gives you strong defaults — most importantly automatic output sanitization that blocks the overwhelming majority of cross-site scripting (XSS) attacks — but those defaults only protect you if you stop fighting them. The practices below cover trusting Angular’s sanitizer, handling auth tokens safely, locking down the page with a Content Security Policy, and keeping your dependency tree free of known vulnerabilities.

Trust Angular’s built-in sanitization

When you bind a value into the DOM with interpolation ({{ }}) or property binding, Angular treats it as untrusted and sanitizes it for the binding context — HTML, style, URL, or resource URL. Script tags and inline event handlers are stripped automatically. This means the common XSS vector of rendering user-supplied HTML is closed by default.

import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-comment',
  standalone: true,
  template: `<div>{{ userComment() }}</div>`,
})
export class CommentComponent {
  // Even if this contains <script>, Angular renders it as inert text.
  readonly userComment = signal('<script>alert("xss")</script> Nice post!');
}

The danger comes from bypassing the sanitizer. Methods like DomSanitizer.bypassSecurityTrustHtml() exist for legitimate cases, but every call is a potential hole. Never pass user input through them.

import { Component, inject, SecurityContext } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Component({ selector: 'app-snippet', standalone: true, template: `<div [innerHTML]="safe"></div>` })
export class SnippetComponent {
  private sanitizer = inject(DomSanitizer);
  // Sanitize explicitly instead of bypassing — returns a cleaned string.
  safe = this.sanitizer.sanitize(SecurityContext.HTML, this.rawInput) ?? '';
  private rawInput = '<b>bold</b><img src=x onerror=steal()>';
}

Treat every bypassSecurityTrust* call as a security review checkpoint. If the value originates from a user, a query string, or a remote API you do not control, do not bypass — sanitize.

Also avoid ElementRef.nativeElement.innerHTML = ..., direct document manipulation, and concatenating user data into template strings — these route around the sanitizer entirely.

Handle authentication tokens securely

Where you store a token determines how exposed it is. localStorage is readable by any JavaScript on the page, so a single XSS flaw leaks the token. The most robust pattern is to keep the session token in an HttpOnly, Secure, SameSite cookie set by the server, which JavaScript cannot read at all. When you must use a bearer token in memory, hold it in a service (a signal) and attach it with a functional interceptor — never log it or persist it.

import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = inject(AuthService).token();
  // Only attach to your own API; never leak tokens to third-party origins.
  if (token && req.url.startsWith('/api/')) {
    req = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } });
  }
  return next(req);
};
import { Injectable, signal } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class AuthService {
  // In-memory only: cleared on reload, never written to localStorage.
  readonly token = signal<string | null>(null);
  setSession(jwt: string) { this.token.set(jwt); }
  clear() { this.token.set(null); }
}

For cookie-based sessions, enable Angular’s built-in CSRF protection by configuring withXsrfConfiguration and having the server issue a CSRF token cookie that Angular echoes back as a header.

Storage optionXSS exposureCSRF exposureSurvives reloadRecommendation
HttpOnly cookieLowNeeds CSRFYesPreferred for session
In-memory signalLowNoneNoGood for SPAs
localStorageHighNoneYesAvoid

Enforce a Content Security Policy

A Content Security Policy (CSP) is a defense-in-depth layer: even if an attacker injects markup, a strict CSP can stop the browser from executing it. Angular supports a nonce-based CSP for its own inline styles. Send the policy as a response header from your server or reverse proxy.

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self' 'nonce-rAnd0m123';
  img-src 'self' data:;
  connect-src 'self' https://api.example.com;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none'

Avoid 'unsafe-inline' and 'unsafe-eval' in script-src — they neutralize most of CSP’s value. Pass the matching nonce to Angular when bootstrapping so its generated styles satisfy the policy.

import { bootstrapApplication } from '@angular/platform-browser';
import { CSP_NONCE } from '@angular/core';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [{ provide: CSP_NONCE, useValue: 'rAnd0m123' }],
});

Audit dependencies regularly

Most real-world breaches exploit known vulnerabilities in third-party packages, not your own code. Make dependency auditing a routine part of CI so a vulnerable transitive dependency fails the build instead of shipping.

npm audit --omit=dev --audit-level=high
npm outdated
ng update @angular/core @angular/cli

Output:

# npm audit report

semver  <7.5.2
Severity: high
Regular Expression Denial of Service - https://github.com/advisories/GHSA-c2qf-rxjj
fix available via `npm audit fix`

1 high severity vulnerability

Keep Angular itself current with ng update, which applies automated migrations, and pin versions with a committed lockfile so every environment installs the exact same tree.

Best Practices

  • Never pass user-controlled data to bypassSecurityTrust*; sanitize explicitly with DomSanitizer.sanitize() when you must process HTML.
  • Prefer HttpOnly Secure cookies for sessions; if using bearer tokens, keep them in memory and attach them only to your own API via an interceptor.
  • Ship a strict CSP without 'unsafe-inline' or 'unsafe-eval', and wire the nonce through CSP_NONCE.
  • Enable Angular’s XSRF protection for cookie-based auth and validate the token server-side.
  • Run npm audit and ng update in CI, fail builds on high-severity findings, and commit your lockfile.
  • Always validate and authorize on the server — client-side guards improve UX but are never a security boundary.
Last updated June 14, 2026
Was this helpful?