The final lesson is a comprehensive security review before deploying the BlogApp. Security hardening is not a single feature — it is a systematic review of every surface: authentication, authorisation, data storage, API communication, and deployment configuration. This checklist covers the full application stack from Angular client to nginx server, ensuring the BlogApp is production-ready from a security standpoint before connecting the Angular frontend to the ASP.NET Core backend in Part 6.
Angular Security Checklist
// ── AUTHENTICATION ────────────────────────────────────────────────────────
// ✅ Access tokens stored in memory (not localStorage/sessionStorage)
// ✅ Refresh tokens in httpOnly, Secure, SameSite=Lax cookies
// ✅ APP_INITIALIZER restores session on page refresh
// ✅ Auto-refresh scheduled before access token expiry
// ✅ Logout invalidates refresh token on server and clears client state
// ✅ withCredentials: true on all authenticated requests
// ── AUTHORISATION ─────────────────────────────────────────────────────────
// ✅ Auth guard on all authenticated routes
// ✅ Admin guard on admin routes (checks Admin role)
// ✅ Routes use canActivate — even if UI hides the link, direct URL must be blocked
// ✅ Lazy-loaded admin routes (admin JS only downloaded by admins)
// ✅ API-side authorisation is the real enforcement (Angular guards are UX only)
// Route guard example:
export const adminGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.hasRole('Admin')) return true;
return router.createUrlTree(['/forbidden']);
};
// ── HTTP SECURITY ─────────────────────────────────────────────────────────
// ✅ Auth interceptor adds Bearer token to all authenticated requests
// ✅ Error interceptor handles 401 (redirect to login), 403 (show forbidden)
// ✅ Token refresh handles concurrent 401 responses (queuing pattern)
// ✅ withCredentials: true for cookie-based auth
// ✅ No sensitive data in URL query parameters (use POST body instead)
// ── XSS PREVENTION ────────────────────────────────────────────────────────
// ✅ No localStorage token storage
// ✅ bypassSecurityTrustHtml used ONLY for server-generated HTML (never user content)
// ✅ [innerHTML] bindings reviewed — Angular sanitises, but review each usage
// ✅ No eval(), document.write(), or innerHTML concatenation
// ✅ Third-party libraries reviewed for known XSS vulnerabilities
// ── ENVIRONMENT AND CONFIGURATION ────────────────────────────────────────
// ✅ API URLs in environment.ts (never hardcoded localhost in production build)
// ✅ ng build --configuration=production uses environment.prod.ts
// ✅ No secrets or API keys in the Angular source code (all secrets on the server)
// ✅ Source maps disabled for production (or served only to authenticated developers)
// ✅ npm audit run — no high/critical vulnerabilities
// ── DEPLOYMENT / NGINX ───────────────────────────────────────────────────
// nginx.conf for serving the Angular app:
// server {
// listen 443 ssl http2;
// server_name blogapp.com;
// root /var/www/blogapp;
// index index.html;
//
// # ── Security headers ─────────────────────────────────────────────
// add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
// add_header X-Frame-Options DENY always;
// add_header X-Content-Type-Options nosniff always;
// add_header Referrer-Policy "strict-origin-when-cross-origin" always;
// add_header Content-Security-Policy "
// default-src 'self';
// script-src 'self';
// style-src 'self' 'unsafe-inline';
// img-src 'self' data: https:;
// connect-src 'self' https://api.blogapp.com;
// frame-ancestors 'none';
// " always;
//
// # ── SPA routing — all paths serve index.html ─────────────────────
// location / {
// try_files $uri $uri/ /index.html;
// }
//
// # ── Cache hashed assets aggressively ─────────────────────────────
// location ~* \.(js|css)$ {
// expires 1y;
// add_header Cache-Control "public, immutable";
// }
// }
try_files $uri $uri/ /index.html directive is essential for Angular SPAs. Without it, navigating directly to https://blogapp.com/posts/my-post returns a 404 because nginx looks for a file at that path (there is none — Angular routes are virtual). This directive tells nginx: try the file path, then the directory, then fall back to index.html. Angular’s router then parses the URL and renders the correct component. All Angular SPA deployments need this configuration.npm audit as part of your CI/CD pipeline and fail the build on high or critical vulnerabilities. Angular applications typically have hundreds of dependencies — any of them can introduce a known vulnerability that attackers target. Set up Dependabot or Renovate to automatically create pull requests for dependency updates. A dependency audit is not a one-time event — it must be continuous. The npm audit fix command automatically upgrades to non-breaking patched versions.ng build --configuration=production which defaults to sourceMap: false in angular.json. If you need source maps for error tracking (Sentry, Datadog), upload them to the error tracking service and delete them from the public CDN after deployment.Pre-Deployment Security Verification
// ── Quick verification commands ───────────────────────────────────────────
// npm audit --audit-level=high // fail if high/critical vulns found
// ng build --configuration=production // verify production build completes
// grep -r 'bypassSecurityTrust' src/ // find all bypass usage — review each
// grep -r 'localStorage' src/ // ensure no token storage in localStorage
// grep -r 'localhost' src/environments/environment.prod.ts // no localhost in prod
// cat dist/blog-app/index.html | grep sourcemap // verify no source maps exposed
Common Mistakes
Mistake 1 — No canActivate guard on authenticated routes (direct URL access bypasses UI guards)
❌ Wrong — hiding the “Admin” link in the nav but no route guard; user types /admin and accesses the page.
✅ Correct — every route requiring auth has canActivate: [authGuard]; hiding nav links is UX, not security.
Mistake 2 — Source maps in production build (exposes TypeScript source)
❌ Wrong — production build includes source maps; anyone can read the original TypeScript in DevTools.
✅ Correct — ng build --configuration=production disables source maps by default; verify with DevTools.