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:
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%:
// 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:
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):
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.
// 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.
// 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:
{
"projects": {
"my-app": {
"architect": {
"build": {
"options": {
"tsConfig": "tsconfig.json"
}
}
}
}
}
}Enable in tsconfig.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
npm audit
npm audit fixFor Angular specifically, keep the Angular core packages in sync — mismatched versions can cause unexpected behavior:
ng update @angular/core @angular/cliUse better-npm-audit for CI integration:
npx better-npm-audit audit --level highFAQ
### 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.