Skip to main content
Back to blog
April 28, 2026|8 min read

Angular Security Best Practices 2026: DomSanitizer, CSP, Route Guards & Audits

Angular security is among the strongest in any frontend framework — but bypassing DomSanitizer is a one-line mistake with severe consequences. This guide covers every critical security pattern in Angular 17+.

ZeriFlow Team

1,115 words

Angular Security Best Practices 2026: DomSanitizer, CSP, Route Guards & Audits

Angular security is built into the framework's DNA. Angular auto-sanitizes HTML bindings, enforces strict template compilation, and provides a DomSanitizer service for explicitly trusting content. However, developers who bypass these protections — often to fix a rendering issue quickly — open serious XSS vulnerabilities.

Get an instant view of your deployed application's security posture with a free scan at ZeriFlow — 80+ checks across headers, TLS, and exposed endpoints.


1. DomSanitizer: Trust Nothing by Default

Angular sanitizes all [innerHTML] bindings by default. The risk comes from bypassing this protection.

This is dangerous:

typescript
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  template: '<div [innerHTML]='trustedHtml'></div>',
})
export class UnsafeComponent {
  trustedHtml: SafeHtml;

  constructor(private sanitizer: DomSanitizer) {
    // NEVER bypass sanitization on user-provided content
    this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(
      userInput // This creates an XSS vulnerability
    );
  }
}

Only bypass sanitization on content you control 100%:

typescript
// Acceptable: content from your own CMS, hardcoded in your codebase
const HARDCODED_ABOUT_HTML = '<p>Our team of <strong>experts</strong>...</p>';
this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(HARDCODED_ABOUT_HTML);

For user-generated rich text, sanitize with DOMPurify first:

typescript
import DOMPurify from 'dompurify';

@Component({
  template: '<div [innerHTML]='sanitizedContent'></div>',
})
export class SafeRichTextComponent {
  @Input() set rawContent(value: string) {
    const clean = DOMPurify.sanitize(value, {
      ALLOWED_TAGS:  ['p', 'b', 'i', 'em', 'strong', 'ul', 'ol', 'li', 'a'],
      ALLOWED_ATTR:  ['href'],
      FORBID_SCRIPTS: true,
    });
    this.sanitizedContent = clean;
  }
  sanitizedContent = '';
}

2. Content Security Policy for Angular

Angular recommends a strict CSP based on nonces. This eliminates the need for 'unsafe-inline' script-src.

In your server (Nginx):

nginx
add_header Content-Security-Policy
  "default-src 'self';
   script-src 'self' 'nonce-{NONCE}';
   style-src 'self' 'nonce-{NONCE}';
   img-src 'self' data: https:;
   connect-src 'self' https://api.yourdomain.com;
   font-src 'self' https://fonts.gstatic.com;
   frame-ancestors 'none';
   object-src 'none'";

Angular's build tooling can inject nonces into inline scripts during SSR. For simpler setups, enable 'strict-dynamic' with hashes:

script-src 'strict-dynamic' 'nonce-{NONCE}' 'unsafe-inline' https:;

'strict-dynamic' causes browsers to ignore the allowlists and only trust nonce/hash-matched scripts, with 'unsafe-inline' as a fallback for browsers that do not support 'strict-dynamic'.

Check your CSP is active with [ZeriFlow](https://zeriflow.com) — it verifies the header is present and parses its directives.


3. HttpClient Interceptors for Security

Angular's HttpClient supports interceptors that can add authentication headers, handle token refresh, and sanitize responses globally.

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

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const auth  = inject(AuthService);
  const token = auth.getAccessToken();

  if (token && req.url.startsWith('https://api.yourdomain.com')) {
    const cloned = req.clone({
      setHeaders: { Authorization: `Bearer ${token}` },
    });
    return next(cloned);
  }

  return next(req);
};

// app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([authInterceptor])),
  ],
};

Never attach auth tokens to requests to third-party domains — the check req.url.startsWith('https://api.yourdomain.com') prevents token leakage to external services.


4. Route Guards for Authentication and Authorization

Angular's route guard system prevents unauthorized navigation at the application layer.

typescript
// auth.guard.ts
import { inject } from '@angular/core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';

export const authGuard: CanActivateFn = (route, state) => {
  const auth   = inject(AuthService);
  const router = inject(Router);

  if (auth.isAuthenticated()) {
    return true;
  }

  return router.createUrlTree(['/login'], {
    queryParams: { returnUrl: state.url },
  });
};

export const roleGuard = (requiredRole: string): CanActivateFn => {
  return () => {
    const auth   = inject(AuthService);
    const router = inject(Router);

    if (auth.hasRole(requiredRole)) return true;
    return router.createUrlTree(['/forbidden']);
  };
};

// app.routes.ts
export const routes: Routes = [
  { path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] },
  {
    path:       'admin',
    component:  AdminComponent,
    canActivate: [authGuard, roleGuard('admin')],
  },
];

Route guards are a UI layer. Always validate authorization on your API server independently.


5. Strict Mode and Trusted Types

Angular 15+ supports Trusted Types, a browser API that prevents DOM XSS by enforcing that only TrustedHTML objects are assigned to .innerHTML.

Enable in angular.json:

json
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "options": {
            "tsConfig": "tsconfig.json"
          }
        }
      }
    }
  }
}

Enable in tsconfig.json:

json
{
  "compilerOptions": {
    "strict":                 true,
    "noImplicitOverride":     true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns":      true,
    "noFallthroughCasesInSwitch": true
  }
}

TypeScript strict mode catches a surprising number of security-relevant bugs at compile time, including null-pointer dereferences that can lead to logic bypasses.


6. Dependency Auditing

bash
npm audit
npm audit fix

For Angular specifically, keep the Angular core packages in sync — mismatched versions can cause unexpected behavior:

bash
ng update @angular/core @angular/cli

Use better-npm-audit for CI integration:

bash
npx better-npm-audit audit --level high

FAQ

### Q: Is Angular's auto-sanitization sufficient to prevent XSS? A: For template interpolation and [innerHTML] bindings, yes — Angular sanitizes by default. The only vectors are explicit bypassSecurityTrust* calls, direct DOM manipulation via ElementRef.nativeElement, and eval(). Avoid all three with user data.

### Q: Should I store JWT tokens in localStorage or cookies? A: Store access tokens in memory (a service property) and refresh tokens in httpOnly, Secure, SameSite=Strict cookies. localStorage is accessible to any JavaScript on your page, including XSS payloads.

### Q: What is Angular's equivalent of React's dangerouslySetInnerHTML? A: DomSanitizer.bypassSecurityTrustHtml(). The name 'bypass' is intentional — Angular makes it hard to ignore that you are explicitly disabling a security feature.

### Q: How does Angular handle CSRF? A: Angular's HttpClient automatically reads the XSRF-TOKEN cookie and sends it as the X-XSRF-TOKEN header. Your backend just needs to set the XSRF-TOKEN cookie and validate the X-XSRF-TOKEN header. Configure the header names if your backend uses different names.

### Q: How often should I update Angular? A: Angular releases a major version every 6 months and provides 18 months of LTS support per version. Run ng update after every minor release and upgrade to the latest major within the LTS window.


Conclusion

Angular security in 2026 is excellent by default — but the defaults are only as strong as your discipline around bypassSecurityTrust*. Use it only on content you control completely, apply DOMPurify on user-generated HTML before rendering, enforce route guards, and keep dependencies current.

After every deployment, run a free ZeriFlow scan to verify your CSP, security headers, and TLS configuration from the outside — exactly as a security researcher or attacker would see them.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading