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)