Security
Comprehensive security guidance for building secure Aurelia 2 applications.
Critical Security Principle
The client cannot be trusted. All security enforcement must happen on the backend. Your Aurelia application's security measures are for user experience and first-line defense only. Always validate, authenticate, and authorize on the server.
What you'll learn...
How Aurelia protects against XSS attacks in templates
Authentication and authorization patterns using the router
CSRF protection strategies
Content Security Policy (CSP) configuration
Secure data handling and storage
Input validation and sanitization
Table of Contents
XSS Prevention in Templates
Cross-Site Scripting (XSS) is one of the most common web vulnerabilities. Aurelia provides built-in protections, but you must understand when they apply.
Text Interpolation (Safe by Default)
Text interpolation using ${} is automatically escaped and safe from XSS:
<!-- SAFE: Text interpolation auto-escapes HTML -->
<div>${userInput}</div>
<p>Welcome, ${username}!</p>
<!-- Even if userInput contains: <script>alert('xss')</script>
It will be rendered as text, not executed -->How it works: Aurelia sets the textContent property, not innerHTML, so the browser automatically escapes HTML entities.
Attribute Binding (Safe by Default)
Attribute bindings are also safe when using standard binding commands:
<!-- SAFE: Attribute values are properly escaped -->
<input value.bind="userInput">
<a href.bind="userProvidedUrl">Link</a>
<img src.bind="userImageUrl" alt.bind="userDescription">URL Injection Risk
While attribute binding escapes HTML, malicious URLs can still be dangerous:
<!-- Potentially dangerous -->
<a href.bind="userUrl">Click me</a>
<!-- If userUrl = "javascript:alert('xss')" this will execute -->Solution: Validate URLs before binding:
sanitizeUrl(url: string): string {
const allowedProtocols = ['http:', 'https:', 'mailto:'];
try {
const parsed = new URL(url, window.location.href);
if (allowedProtocols.includes(parsed.protocol)) {
return url;
}
} catch {
// Invalid URL
}
return '#'; // Safe fallback
}innerHTML Binding (Dangerous - Requires Sanitization)
The innerHTML binding is where XSS vulnerabilities occur. Never bind user input directly to innerHTML without sanitization.
<!-- DANGEROUS: User input rendered as HTML -->
<div innerHTML.bind="userContent"></div>
<!-- If userContent contains: <img src=x onerror="alert('xss')">
It will execute the malicious script --><!-- SAFE: Content is sanitized before rendering -->
<div innerHTML.bind="userContent | sanitize"></div>Implementing HTML Sanitization
Aurelia provides a sanitize value converter interface but requires you to provide the sanitization implementation.
Step 1: Install a sanitization library
npm install dompurify
npm install --save-dev @types/dompurifyStep 2: Create a sanitizer service
// src/services/html-sanitizer.ts
import { DI } from '@aurelia/kernel';
import { ISanitizer } from '@aurelia/runtime-html';
import DOMPurify from 'dompurify';
export class HtmlSanitizer implements ISanitizer {
sanitize(input: string): string {
return DOMPurify.sanitize(input, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href', 'target', 'rel'],
ALLOW_DATA_ATTR: false,
});
}
}
// Register as the ISanitizer implementation
export const HtmlSanitizerRegistration = DI.createInterface<ISanitizer>(
'ISanitizer',
x => x.singleton(HtmlSanitizer)
);Step 3: Register the sanitizer
// src/main.ts
import Aurelia from 'aurelia';
import { HtmlSanitizerRegistration } from './services/html-sanitizer';
Aurelia
.register(HtmlSanitizerRegistration)
.app(component)
.start();Step 4: Use in templates
<!-- Now the sanitize value converter will use your implementation -->
<div innerHTML.bind="userContent | sanitize"></div>Expression Parser Security
Aurelia's binding expression parser has built-in security features:
Restricted Globals: Only safe globals are accessible in binding expressions:
<!-- ALLOWED: Safe built-in functions -->
<div>${JSON.stringify(data)}</div>
<div>${Math.round(price)}</div>
<div>${parseInt(value)}</div>
<!-- BLOCKED: Dangerous globals not accessible -->
<div>${window.location.href}</div> <!-- Error: window is not accessible -->
<div>${document.cookie}</div> <!-- Error: document is not accessible -->
<div>${eval('malicious')}</div> <!-- Error: eval is not accessible -->Allowed globals: Array, Boolean, Date, JSON, Math, Number, Object, RegExp, String, Intl, Map, Set, BigInt, Infinity, NaN, isFinite, isNaN, parseFloat, parseInt, decodeURI, decodeURIComponent, encodeURI, encodeURIComponent
No eval or Function constructor: Aurelia never uses eval() or new Function(), making it safe for Content Security Policy (CSP).
Authentication Patterns
Authentication verifies user identity. Aurelia provides the tools to create robust authentication flows using dependency injection and router hooks.
Creating an Authentication Service
Use Aurelia's DI container to create a singleton authentication service:
// src/services/auth-service.ts
import { DI, resolve } from '@aurelia/kernel';
import { IHttpClient } from '@aurelia/fetch-client';
export interface IAuthService {
isAuthenticated(): boolean;
login(credentials: Credentials): Promise<void>;
logout(): Promise<void>;
getToken(): string | null;
}
export const IAuthService = DI.createInterface<IAuthService>(
'IAuthService',
x => x.singleton(AuthService)
);
export interface Credentials {
username: string;
password: string;
}
interface AuthResponse {
token: string;
user: {
id: string;
username: string;
email: string;
};
}
export class AuthService implements IAuthService {
private token: string | null = null;
private user: AuthResponse['user'] | null = null;
private http = resolve(IHttpClient);
constructor() {
// Restore auth state from storage on initialization
this.token = sessionStorage.getItem('auth_token');
const userJson = sessionStorage.getItem('auth_user');
if (userJson) {
try {
this.user = JSON.parse(userJson);
} catch {
this.logout();
}
}
}
isAuthenticated(): boolean {
return this.token !== null && this.user !== null;
}
async login(credentials: Credentials): Promise<void> {
const response = await this.http.fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify(credentials),
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) {
throw new Error('Login failed');
}
const data: AuthResponse = await response.json();
this.token = data.token;
this.user = data.user;
// Store auth state (see "Secure Token Storage" section)
sessionStorage.setItem('auth_token', this.token);
sessionStorage.setItem('auth_user', JSON.stringify(this.user));
}
async logout(): Promise<void> {
// Notify server
if (this.token) {
await this.http.fetch('/api/auth/logout', {
method: 'POST',
headers: { 'Authorization': `Bearer ${this.token}` }
}).catch(() => {
// Ignore errors on logout
});
}
// Clear local state
this.token = null;
this.user = null;
sessionStorage.removeItem('auth_token');
sessionStorage.removeItem('auth_user');
}
getToken(): string | null {
return this.token;
}
getUser() {
return this.user;
}
}Adding Authentication Headers
Use an HTTP interceptor to automatically add authentication tokens to requests:
// src/interceptors/auth-interceptor.ts
import { resolve } from '@aurelia/kernel';
import { IHttpClient } from '@aurelia/fetch-client';
import { IAuthService } from '../services/auth-service';
export class AuthInterceptor {
private http = resolve(IHttpClient);
private auth = resolve(IAuthService);
register() {
this.http.configure(config => {
config.useInterceptor({
request(request) {
const token = this.auth.getToken();
if (token) {
request.headers.set('Authorization', `Bearer ${token}`);
}
return request;
}.bind(this),
response(response) {
// Handle 401 Unauthorized
if (response.status === 401) {
this.auth.logout();
window.location.href = '/login';
}
return response;
}.bind(this)
});
});
}
}Register the interceptor during app startup:
// src/main.ts
import Aurelia, { AppTask } from 'aurelia';
import { AuthInterceptor } from './interceptors/auth-interceptor';
Aurelia
.register(
AppTask.creating(AuthInterceptor, interceptor => interceptor.register())
)
.app(component)
.start();Login Component Example
// src/pages/login.ts
import { resolve } from '@aurelia/kernel';
import { IRouter } from '@aurelia/router';
import { IAuthService, Credentials } from '../services/auth-service';
export class Login {
private credentials: Credentials = { username: '', password: '' };
private error: string = '';
private loading: boolean = false;
private auth = resolve(IAuthService);
private router = resolve(IRouter);
async login() {
this.error = '';
this.loading = true;
try {
await this.auth.login(this.credentials);
await this.router.load('/dashboard');
} catch (err) {
this.error = 'Invalid username or password';
} finally {
this.loading = false;
}
}
}<!-- src/pages/login.html -->
<form submit.trigger="login()">
<h1>Login</h1>
<div if.bind="error" class="error">
${error}
</div>
<div>
<label>
Username:
<input type="text" value.bind="credentials.username" disabled.bind="loading">
</label>
</div>
<div>
<label>
Password:
<input type="password" value.bind="credentials.password" disabled.bind="loading">
</label>
</div>
<button type="submit" disabled.bind="loading">
${loading ? 'Logging in...' : 'Login'}
</button>
</form>Authorization with Route Guards
Authorization determines what authenticated users can access. Use Aurelia's router lifecycle hooks to implement route guards.
Basic Route Guard with canLoad
The canLoad hook runs before navigating to a route. Return false or a redirect to prevent access:
// src/pages/dashboard.ts
import { resolve } from '@aurelia/kernel';
import { IRouter } from '@aurelia/router';
import { IAuthService } from '../services/auth-service';
import type { RouteNode, Params } from '@aurelia/router';
export class Dashboard {
private auth = resolve(IAuthService);
private router = resolve(IRouter);
canLoad(params: Params, next: RouteNode, current: RouteNode | null) {
if (!this.auth.isAuthenticated()) {
// Redirect to login
return this.router.load('/login', {
queryParams: { returnUrl: next.computeAbsolutePath() }
});
}
return true;
}
}Role-Based Authorization
Extend the auth service to support roles:
// src/services/auth-service.ts
export interface IAuthService {
// ... existing methods
hasRole(role: string): boolean;
hasAnyRole(...roles: string[]): boolean;
hasAllRoles(...roles: string[]): boolean;
}
export class AuthService implements IAuthService {
private roles: string[] = [];
// ... existing implementation
hasRole(role: string): boolean {
return this.roles.includes(role);
}
hasAnyRole(...roles: string[]): boolean {
return roles.some(role => this.roles.includes(role));
}
hasAllRoles(...roles: string[]): boolean {
return roles.every(role => this.roles.includes(role));
}
}Use role checks in route guards:
// src/pages/admin.ts
import { resolve } from '@aurelia/kernel';
import { IRouter } from '@aurelia/router';
import { IAuthService } from '../services/auth-service';
export class AdminPanel {
private auth = resolve(IAuthService);
private router = resolve(IRouter);
canLoad() {
if (!this.auth.isAuthenticated()) {
return this.router.load('/login');
}
if (!this.auth.hasRole('admin')) {
// Redirect to forbidden page or home
return this.router.load('/forbidden');
}
return true;
}
}Reusable Authorization Guard
Create a reusable guard for multiple routes:
// src/guards/require-auth.ts
import { DI } from '@aurelia/kernel';
import { IRouter, RouteNode, Params } from '@aurelia/router';
import { IAuthService } from '../services/auth-service';
export interface IRequireAuthOptions {
roles?: string[];
requireAll?: boolean; // Require all roles or any role
redirectTo?: string;
}
export const IRequireAuth = DI.createInterface<IRequireAuth>('IRequireAuth');
export interface IRequireAuth {
canLoad(
params: Params,
next: RouteNode,
current: RouteNode | null,
options?: IRequireAuthOptions
): boolean | Promise<boolean> | any;
}
export class RequireAuth implements IRequireAuth {
private auth = resolve(IAuthService);
private router = resolve(IRouter);
canLoad(
params: Params,
next: RouteNode,
current: RouteNode | null,
options: IRequireAuthOptions = {}
) {
const { roles, requireAll = false, redirectTo = '/login' } = options;
// Check authentication
if (!this.auth.isAuthenticated()) {
return this.router.load(redirectTo, {
queryParams: { returnUrl: next.computeAbsolutePath() }
});
}
// Check authorization
if (roles && roles.length > 0) {
const hasAccess = requireAll
? this.auth.hasAllRoles(...roles)
: this.auth.hasAnyRole(...roles);
if (!hasAccess) {
return this.router.load('/forbidden');
}
}
return true;
}
}Use the reusable guard:
// src/pages/reports.ts
import { resolve } from '@aurelia/kernel';
import { IRequireAuth } from '../guards/require-auth';
export class Reports {
private requireAuth = resolve(IRequireAuth);
canLoad(params: Params, next: RouteNode, current: RouteNode | null) {
return this.requireAuth.canLoad(params, next, current, {
roles: ['admin', 'manager'],
requireAll: false // Any of these roles
});
}
}Protecting Multiple Routes
Apply guards to multiple routes via route configuration:
// src/my-app.ts
import { route } from '@aurelia/router';
import { Dashboard } from './pages/dashboard';
import { Reports } from './pages/reports';
import { AdminPanel } from './pages/admin';
@route({
routes: [
{ path: '', component: Home },
{ path: 'login', component: Login },
{ path: 'dashboard', component: Dashboard }, // Has its own canLoad
{ path: 'reports', component: Reports }, // Has its own canLoad
{ path: 'admin', component: AdminPanel }, // Has its own canLoad
]
})
export class MyApp {}CSRF Protection
Cross-Site Request Forgery (CSRF) attacks trick authenticated users into making unwanted requests. Protect against CSRF using tokens.
CSRF Token Pattern
How it works:
Server generates a unique CSRF token per session
Token is included in forms or sent with requests
Server validates the token on state-changing requests (POST, PUT, DELETE)
Implementation with Meta Tag
Many frameworks provide CSRF tokens via meta tags:
<!-- Server renders this in your main HTML -->
<meta name="csrf-token" content="abc123...">Read and send the token with requests:
// src/services/csrf.ts
import { DI } from '@aurelia/kernel';
export const ICsrfService = DI.createInterface<ICsrfService>(
'ICsrfService',
x => x.singleton(CsrfService)
);
export interface ICsrfService {
getToken(): string | null;
}
export class CsrfService implements ICsrfService {
private token: string | null;
constructor() {
const meta = document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]');
this.token = meta?.content ?? null;
}
getToken(): string | null {
return this.token;
}
}Add CSRF token to requests via interceptor:
// src/interceptors/csrf-interceptor.ts
import { resolve } from '@aurelia/kernel';
import { IHttpClient } from '@aurelia/fetch-client';
import { ICsrfService } from '../services/csrf';
export class CsrfInterceptor {
private http = resolve(IHttpClient);
private csrf = resolve(ICsrfService);
register() {
this.http.configure(config => {
config.useInterceptor({
request(request) {
// Add CSRF token to state-changing requests
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(request.method)) {
const token = this.csrf.getToken();
if (token) {
request.headers.set('X-CSRF-Token', token);
}
}
return request;
}.bind(this)
});
});
}
}SameSite Cookies
Modern browsers support the SameSite cookie attribute, which provides automatic CSRF protection:
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure; HttpOnlyServer configuration (example with Express.js):
app.use(session({
cookie: {
sameSite: 'strict', // or 'lax'
secure: true, // HTTPS only
httpOnly: true // Not accessible via JavaScript
}
}));Content Security Policy
Content Security Policy (CSP) is an HTTP header that tells browsers which resources are allowed to load, preventing XSS and data injection attacks.
Aurelia's CSP Compatibility
Aurelia is fully compatible with strict CSP policies because:
✅ No use of
eval()ornew Function()✅ Templates are pre-compiled, not evaluated at runtime
✅ All code is in JavaScript files, not inline scripts
Recommended CSP Configuration
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';Explanation:
default-src 'self': Only allow resources from same originscript-src 'self': Only load scripts from your domain (no inline scripts)style-src 'self' 'unsafe-inline': Allow inline styles (needed for style attribute bindings)connect-src 'self' https://api.example.com: Allow AJAX to your APIframe-ancestors 'none': Prevent clickjacking (same as X-Frame-Options: DENY)
Inline Styles in Aurelia
Aurelia's style attribute binding generates inline styles:
<div style.bind="dynamicStyles"></div>This requires 'unsafe-inline' in style-src. For stricter CSP:
Use CSS classes instead of inline styles
Use
style-src 'self' 'nonce-...'with nonces (advanced)
Testing Your CSP
Use report-only mode during development to find violations without blocking resources:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reportBrowser console will show CSP violations, and violations will be POSTed to /csp-report endpoint.
CSP Best Practices
Start strict, then relax if needed - Begin with
default-src 'self'and add exceptionsNever use
'unsafe-eval'- Aurelia doesn't need itAvoid
'unsafe-inline'for scripts - Aurelia doesn't need it for scriptsUse HTTPS only -
upgrade-insecure-requestsdirectiveTest thoroughly - Use report-only mode first
Secure Token Storage
How you store authentication tokens affects security. Each method has trade-offs.
Storage Options Comparison
localStorage
✅ Yes
❌ No
✅ Yes
✅ Yes
sessionStorage
✅ Yes
❌ No
❌ No
✅ Yes
Cookie (HttpOnly)
❌ No
✅ Yes (without SameSite)
✅ Yes
❌ No
Memory only
❌ No
❌ No
❌ No
⚠️ Limited
Recommended Approach: HttpOnly Cookies
Most secure option: Store tokens in HttpOnly cookies set by the server.
Server sets cookie:
Set-Cookie: token=abc123; HttpOnly; Secure; SameSite=Strict; Path=/Benefits:
✅ Not accessible via JavaScript (prevents XSS token theft)
✅ Automatically sent with requests (no client-side code needed)
✅
SameSiteattribute prevents CSRF
Client-side implementation:
// src/services/auth-service.ts
export class AuthService implements IAuthService {
// No token storage in JavaScript!
// Token is in HttpOnly cookie managed by server
async login(credentials: Credentials): Promise<void> {
const response = await this.http.fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify(credentials),
credentials: 'include' // Include cookies in request
});
if (response.ok) {
// Server sets HttpOnly cookie
// No need to store token in JavaScript
this.authenticated = true;
}
}
async logout(): Promise<void> {
await this.http.fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include'
});
this.authenticated = false;
}
async checkAuth(): Promise<boolean> {
const response = await this.http.fetch('/api/auth/me', {
credentials: 'include'
});
this.authenticated = response.ok;
return this.authenticated;
}
}Configure fetch client to include cookies:
// src/main.ts
import Aurelia from 'aurelia';
import { IHttpClient } from '@aurelia/fetch-client';
Aurelia.register({
register(container) {
const http = container.get(IHttpClient);
http.configure(config => {
config
.withBaseUrl('https://api.example.com')
.withDefaults({
credentials: 'include', // Always include cookies
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
});
}
});Alternative: sessionStorage with Short Expiry
If you must store tokens in JavaScript (e.g., token-based API without cookies):
Best practices:
Use
sessionStorageinstead oflocalStorage(cleared on tab close)Use short-lived tokens (15-30 minutes)
Implement refresh token rotation
Clear on logout
export class AuthService implements IAuthService {
private readonly TOKEN_KEY = 'auth_token';
private readonly REFRESH_KEY = 'refresh_token';
async login(credentials: Credentials): Promise<void> {
const response = await this.http.fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify(credentials)
});
const { accessToken, refreshToken } = await response.json();
// Store in sessionStorage (cleared on tab close)
sessionStorage.setItem(this.TOKEN_KEY, accessToken);
sessionStorage.setItem(this.REFRESH_KEY, refreshToken);
}
async refreshAccessToken(): Promise<void> {
const refreshToken = sessionStorage.getItem(this.REFRESH_KEY);
if (!refreshToken) {
throw new Error('No refresh token');
}
const response = await this.http.fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Authorization': `Bearer ${refreshToken}` }
});
const { accessToken } = await response.json();
sessionStorage.setItem(this.TOKEN_KEY, accessToken);
}
logout(): void {
sessionStorage.removeItem(this.TOKEN_KEY);
sessionStorage.removeItem(this.REFRESH_KEY);
}
}Never Store Sensitive Data in localStorage/sessionStorage
If an XSS vulnerability exists, attackers can steal tokens from localStorage/sessionStorage. HttpOnly cookies are much safer.
Input Validation and Sanitization
Always validate and sanitize user input—both client-side and server-side.
Client-Side Validation
Use Aurelia's validation plugin for user experience:
import { newInstanceForScope, resolve } from '@aurelia/kernel';
import { IValidationRules } from '@aurelia/validation';
import { IValidationController } from '@aurelia/validation-html';
export class UserForm {
private user = {
email: '',
age: null,
website: ''
};
private validation = resolve(newInstanceForScope(IValidationController));
private validationRules = resolve(IValidationRules);
constructor() {
this.validationRules
.on(this.user)
.ensure('email')
.required()
.email()
.maxLength(255)
.ensure('age')
.required()
.satisfies((value: number) => value >= 18 && value <= 120)
.withMessage('Age must be between 18 and 120')
.ensure('website')
.satisfiesRule('url') // Custom rule
.maxLength(2048);
}
async submit() {
const result = await this.validation.validate();
if (!result.valid) {
return; // Show validation errors
}
// Submit to server
await this.saveUser(this.user);
}
}See: Validation documentation for complete guide.
Custom Validation Rules
Create custom validators for security-sensitive inputs:
// src/validation/custom-rules.ts
import { IValidationRules } from '@aurelia/validation';
export function registerCustomRules(rules: IValidationRules) {
// URL validation
rules.customRule(
'url',
(value: string) => {
if (!value) return true;
try {
const url = new URL(value);
return ['http:', 'https:'].includes(url.protocol);
} catch {
return false;
}
},
'Must be a valid HTTP/HTTPS URL'
);
// Safe filename (no path traversal)
rules.customRule(
'safeFilename',
(value: string) => {
if (!value) return true;
return !/[\/\\]/.test(value) && !/^\.\./.test(value);
},
'Invalid filename'
);
// No HTML tags
rules.customRule(
'noHtml',
(value: string) => {
if (!value) return true;
return !/<[^>]*>/.test(value);
},
'HTML tags are not allowed'
);
}Server-Side Validation (Critical)
Client-side validation is for UX only. Always re-validate on the server:
// Example: Server-side validation (Node.js/Express)
app.post('/api/users', async (req, res) => {
// Validate input
const errors = [];
if (!isEmail(req.body.email)) {
errors.push({ field: 'email', message: 'Invalid email' });
}
if (req.body.age < 18 || req.body.age > 120) {
errors.push({ field: 'age', message: 'Invalid age' });
}
if (errors.length > 0) {
return res.status(400).json({ errors });
}
// Sanitize input before saving
const user = {
email: validator.normalizeEmail(req.body.email),
age: parseInt(req.body.age, 10),
name: sanitizeHtml(req.body.name, { allowedTags: [] }) // Strip all HTML
};
await db.users.create(user);
res.json({ success: true });
});Never Trust Client Input
An attacker can bypass client-side validation using browser dev tools or by crafting HTTP requests directly. Always validate and sanitize on the server.
Secure Communication
HTTPS Only
Always use HTTPS in production. HTTP transmits data in plaintext, exposing:
Passwords and tokens
Session cookies
User data
API requests/responses
Enforce HTTPS:
Redirect HTTP to HTTPS (server configuration)
Use HSTS header:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadUse
Secureflag on cookies
Check protocol in app:
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
location.href = 'https:' + window.location.href.substring(window.location.protocol.length);
}API Security
Configure fetch client securely:
import { IHttpClient } from '@aurelia/fetch-client';
export class ApiConfig {
static configure(http: IHttpClient) {
http.configure(config => {
config
.withBaseUrl('https://api.example.com')
.withDefaults({
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.withInterceptor({
request(request) {
// Add security headers
request.headers.set('X-Requested-With', 'XMLHttpRequest');
return request;
},
response(response) {
// Validate response
const contentType = response.headers.get('Content-Type');
if (!contentType?.includes('application/json')) {
throw new Error('Invalid response type');
}
return response;
}
});
});
}
}See: Fetch Client documentation
Security Checklist
Use this checklist to audit your Aurelia application security:
Templates & Data Binding
Authentication
Authorization
Token Storage
CSRF Protection
Content Security Policy
Input Validation
Communication Security
Error Handling
Dependencies
Deployment
Related Documentation
Securing an App (Recipe) - Basic security overview
Authentication with Auth0 - Third-party authentication integration
Router Hooks - Lifecycle hooks for route guards
Validation Plugin - Client-side input validation
Fetch Client - HTTP client configuration
Dependency Injection - DI for services
Additional Resources
External Security Resources
OWASP Top 10 - Most critical web security risks
OWASP Cheat Sheet Series - Security best practices
MDN Web Security - Browser security features
Content Security Policy Reference - CSP guide
Security Testing Tools
Last updated
Was this helpful?