Security

Comprehensive security guidance for building secure Aurelia 2 applications.

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">

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 -->

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/dompurify

Step 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>

DOMPurify Configuration

Customize DOMPurify based on your needs:

  • ALLOWED_TAGS: Whitelist of allowed HTML tags

  • ALLOWED_ATTR: Whitelist of allowed attributes

  • FORBID_TAGS: Blacklist of forbidden tags

  • FORBID_ATTR: Blacklist of forbidden attributes

See DOMPurify documentation for all options.

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:

  1. Server generates a unique CSRF token per session

  2. Token is included in forms or sent with requests

  3. 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; HttpOnly

Server configuration (example with Express.js):

app.use(session({
  cookie: {
    sameSite: 'strict', // or 'lax'
    secure: true,       // HTTPS only
    httpOnly: true      // Not accessible via JavaScript
  }
}));

SameSite Options

  • Strict: Cookie never sent in cross-site requests (strongest protection)

  • Lax: Cookie sent on top-level navigation (GET), not on cross-site POST

  • None: Cookie sent in all contexts (requires Secure flag)

For most applications, SameSite=Lax provides good CSRF protection without breaking legitimate use cases.


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() or new Function()

  • ✅ Templates are pre-compiled, not evaluated at runtime

  • ✅ All code is in JavaScript files, not inline scripts

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 origin

  • script-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 API

  • frame-ancestors 'none': Prevent clickjacking (same as X-Frame-Options: DENY)

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-report

Browser console will show CSP violations, and violations will be POSTed to /csp-report endpoint.

CSP Best Practices

  1. Start strict, then relax if needed - Begin with default-src 'self' and add exceptions

  2. Never use 'unsafe-eval' - Aurelia doesn't need it

  3. Avoid 'unsafe-inline' for scripts - Aurelia doesn't need it for scripts

  4. Use HTTPS only - upgrade-insecure-requests directive

  5. Test thoroughly - Use report-only mode first


Secure Token Storage

How you store authentication tokens affects security. Each method has trade-offs.

Storage Options Comparison

Storage Method
XSS Vulnerable
CSRF Vulnerable
Persists on Close
Accessible via JS

localStorage

✅ Yes

❌ No

✅ Yes

✅ Yes

sessionStorage

✅ Yes

❌ No

❌ No

✅ Yes

Cookie (HttpOnly)

❌ No

✅ Yes (without SameSite)

✅ Yes

❌ No

Memory only

❌ No

❌ No

❌ No

⚠️ Limited

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)

  • SameSite attribute 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:

  1. Use sessionStorage instead of localStorage (cleared on tab close)

  2. Use short-lived tokens (15-30 minutes)

  3. Implement refresh token rotation

  4. 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);
  }
}

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 });
});

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:

  1. Redirect HTTP to HTTPS (server configuration)

  2. Use HSTS header:

    Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  3. Use Secure flag 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



Additional Resources

External Security Resources

Security Testing Tools

  • OWASP ZAP - Security scanner

  • npm audit - Dependency vulnerability scanner

  • Snyk - Dependency and code security scanner

Stay Informed

Security is an ongoing process, not a one-time task. Subscribe to security advisories for your backend framework and dependencies. Regularly review and update your security measures.

Last updated

Was this helpful?