Skip to main content

Angular Security

Angular has security built into its DNA, providing many security measures by default that require manual implementation in React/Next.js. These built-in security features are a major advantage in large-scale enterprise development.

1. XSS (Cross-Site Scripting) Prevention

Default Sanitization

Angular automatically escapes all values in templates. Interpolation expressions ({{ }}) are converted to HTML entities, preventing script injection.

// Component
@Component({
template: `<p>{{ userInput }}</p>`
})
export class SafeComponent {
userInput = '<script>alert("XSS")</script>';
// Rendered as: &lt;script&gt;alert("XSS")&lt;/script&gt;
}

Unlike React's dangerouslySetInnerHTML, Angular requires explicit opt-in for potentially dangerous operations.

[innerHTML] Binding Sanitization

Even when using [innerHTML] binding, Angular automatically sanitizes the HTML. <script> tags and dangerous attributes like onerror are removed.

@Component({
template: `<div [innerHTML]="htmlContent"></div>`
})
export class HtmlComponent {
// <script> tags are automatically removed
htmlContent = '<b>Bold text</b><script>alert("XSS")</script>';
// Output: <b>Bold text</b>
}

DomSanitizer Service

Use DomSanitizer to explicitly insert trusted content. However, misuse can introduce XSS vulnerabilities, so handle it with care.

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

@Component({
selector: 'app-trusted',
template: `
<div [innerHTML]="trustedHtml"></div>
<a [href]="trustedUrl">Link</a>
`
})
export class TrustedComponent {
trustedHtml: SafeHtml;
trustedUrl: SafeUrl;

constructor(private sanitizer: DomSanitizer) {
// ⚠️ Only use with content from trusted sources
this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(
'<b>Admin-approved content</b>'
);
// ⚠️ Never use with user input
this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl(
'https://example.com/safe-page'
);
}
}
MethodUse CaseRisk
bypassSecurityTrustHtmlHTML insertionScript injection
bypassSecurityTrustUrlhref/src URLsjavascript: scheme
bypassSecurityTrustResourceUrliframe/script URLsLoading external code
bypassSecurityTrustStyleInline stylesCSS injection
warning

Never use bypassSecurityTrust* with user input. Restrict its use to content that has been validated and sanitized on the server side.

2. CSRF Token (XSRF) Protection

Built-in XSRF Protection in HttpClient

Angular's HttpClient has built-in support for cookie-based XSRF protection. When the server sets a cookie named XSRF-TOKEN, HttpClient automatically appends it as an X-XSRF-TOKEN header on each request.

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

export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withXsrfConfiguration({
cookieName: 'XSRF-TOKEN', // Cookie name set by server (default)
headerName: 'X-XSRF-TOKEN', // Header name added to requests (default)
})
)
]
};

Server-Side Configuration (ASP.NET Core)

// Program.cs
builder.Services.AddAntiforgery(options =>
{
options.Cookie.Name = "XSRF-TOKEN";
options.HeaderName = "X-XSRF-TOKEN";
options.Cookie.SameSite = SameSiteMode.Strict;
});

// Make XSRF-TOKEN cookie readable by Angular (HttpOnly=false)
app.Use(async (context, next) =>
{
var antiforgery = context.RequestServices.GetRequiredService<IAntiforgery>();
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
new CookieOptions { HttpOnly = false });
await next();
});
note

React/Next.js requires manual CSRF protection implementation, while Angular's HttpClient provides this mechanism out of the box.

3. Content Security Policy (CSP)

Basic CSP Configuration

CSP (Content Security Policy) is an HTTP header that instructs browsers about allowable resource loading policies. It prevents secondary damage from XSS (i.e., script execution).

# Server-side HTTP header example
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM_NONCE}';
style-src 'self' 'nonce-{RANDOM_NONCE}';
img-src 'self' data: https://trusted-cdn.example.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';

Angular 16+ recommends nonce-based CSP. By passing a server-generated random nonce value to Angular, inline styles can be used in a CSP-compliant manner.

<!-- index.html: Angular reads the nonce attribute -->
<app-root ngCspNonce="{SERVER_GENERATED_NONCE}"></app-root>
// Angular side: CSP nonce configuration
import { CSP_NONCE } from '@angular/core';

export const appConfig: ApplicationConfig = {
providers: [
{
provide: CSP_NONCE,
useValue: globalThis.myRandomNonce, // Nonce value passed from server
}
]
};

ViewEncapsulation and style-src

Angular's default ViewEncapsulation (Emulated) injects inline styles, which may require style-src 'unsafe-inline'. Using nonces resolves this issue.

// Component-level configuration
@Component({
selector: 'app-root',
encapsulation: ViewEncapsulation.ShadowDom, // Use Shadow DOM to isolate styles
})
export class AppComponent {}

4. Trusted Types

Trusted Types API Overview

Trusted Types is a browser API that makes DOM operations type-safe to prevent XSS. It prohibits direct string assignment to dangerous sinks like innerHTML and eval, accepting only trusted objects.

Angular's Trusted Types Support

Angular natively supports Trusted Types, and all internal DOM operations are performed through Trusted Types policies.

# Enable Trusted Types via CSP header
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types angular
// Angular's Trusted Types policy name is 'angular'
// When a custom policy is needed
const policy = trustedTypes.createPolicy('myAppPolicy', {
createHTML: (input: string) => {
// Custom sanitization logic
return DOMPurify.sanitize(input);
}
});
note

Trusted Types support varies by browser (major browsers are now supported). Consider using a polyfill for broader compatibility.

5. Authentication & Authorization Patterns

Authentication Token Management with HTTP Interceptors

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

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

if (token) {
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next(authReq);
}
return next(req);
};

// Register in app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([authInterceptor]))
]
};

Route-Level Authorization with Guards

// auth.guard.ts (functional guard - Angular 15+)
import { inject } from '@angular/core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';

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

if (authService.isAuthenticated()) {
return true;
}
// Redirect to login page when unauthenticated
return router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url }
});
};

// app.routes.ts
export const routes: Routes = [
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [authGuard]
},
{
path: 'admin',
loadComponent: () => import('./admin/admin.component'),
canActivate: [authGuard, adminGuard] // Combining multiple guards
}
];

Element-Level Authorization with Custom Directives

// has-role.directive.ts
@Directive({
selector: '[appHasRole]',
standalone: true
})
export class HasRoleDirective implements OnInit {
@Input('appHasRole') requiredRole!: string | string[];

constructor(
private templateRef: TemplateRef<unknown>,
private viewContainer: ViewContainerRef,
private authService: AuthService
) {}

ngOnInit() {
const roles = Array.isArray(this.requiredRole)
? this.requiredRole
: [this.requiredRole];

if (this.authService.hasAnyRole(roles)) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}

// Usage
// <button *appHasRole="'admin'">Admin-only button</button>
// <div *appHasRole="['editor', 'admin']">Content for editors and admins</div>

JWT Token Refresh Pattern

// auth.interceptor.ts (with token refresh)
import { catchError, switchMap, throwError } from 'rxjs';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);

return next(addToken(req, authService.getToken())).pipe(
catchError(error => {
if (error.status === 401 && !req.url.includes('/auth/refresh')) {
// Access token expired → attempt refresh
return authService.refreshToken().pipe(
switchMap(newToken => {
return next(addToken(req, newToken));
}),
catchError(refreshError => {
authService.logout(); // Logout if refresh also fails
return throwError(() => refreshError);
})
);
}
return throwError(() => error);
})
);
};

function addToken(req: HttpRequest<unknown>, token: string | null) {
return token ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }) : req;
}
note

Compared to React: React requires implementing auth logic individually with custom hooks or context. Angular enables unified auth/authz management through interceptors (HTTP layer) + guards (route layer) + directives (UI layer).

6. Secure API Requests

withCredentials and HttpOnly Cookies

// withCredentials configuration in HttpClient
@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(private http: HttpClient) {}

getData() {
return this.http.get('/api/data', {
withCredentials: true // Send HttpOnly cookies
});
}
}

// Using an interceptor for global configuration
export const credentialsInterceptor: HttpInterceptorFn = (req, next) => {
const credentialReq = req.clone({ withCredentials: true });
return next(credentialReq);
};

CORS and Preflight Requests

CORS (Cross-Origin Resource Sharing) is a server-side concern. When an Angular app makes requests to a different origin, the browser sends a preflight request (OPTIONS) first.

// Server-side CORS configuration (ASP.NET Core)
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAngularApp", policy =>
{
policy.WithOrigins("https://yourapp.example.com") // Don't use wildcard (*)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials(); // Required when using withCredentials
});
});
warning

AllowAnyOrigin() and AllowCredentials() cannot be used together (prohibited for security reasons). Always specify concrete origins.

7. Security Auditing and Dependencies

Leveraging npm audit

# Check for vulnerabilities
npm audit

# Auto-fix (patch versions only)
npm audit fix

# Detailed report (JSON format)
npm audit --json

# CI/CD pipeline usage (fail build on high-severity vulnerabilities)
npm audit --audit-level=high

Automatic Patching with ng update

# Check for Angular and dependency updates
ng update

# Update Angular core
ng update @angular/core @angular/cli

# Apply all compatible updates
ng update --all

ng update automatically runs migration scripts during major version updates, significantly reducing manual fixes.

Dependency Vulnerability Management

// package.json: Strict version management
{
"dependencies": {
"@angular/core": "~18.0.0" // Tilde (~) auto-updates patch versions only
}
}
# Integration with third-party tools like Snyk
npx snyk test
npx snyk monitor

# Using GitHub Dependabot (.github/dependabot.yml)
# Automatically creates PRs for npm package updates on a schedule

Angular Security Configuration Checklist

ItemRecommended Setting
bypassSecurityTrust* usageNever use with user input
XSRF protectionEnable with withXsrfConfiguration()
CSP headersConfigure with nonce-based policy
withCredentialsEnable only when necessary
CORSSpecify concrete origins
DependenciesRun npm audit + ng update regularly
Trusted TypesEnable via CSP header (recommended)