Angular Best Practices : sécurité API et auth

🏷️ Front-end 📅 13/04/2026 13:00:00 👤 Mezgani said
Angular Best Practices Security Api Interceptor
Angular Best Practices : sécurité API et auth

Interceptors, guards, gestion des tokens, erreurs HTTP et sanitization : les bonnes pratiques Angular pour sécuriser les échanges avec votre API.

Responsabilites frontend vs backend

Une idee recue frequente est de penser qu'une application Angular "securisee" est une application qui ne se fait pas pirater. En realite, la securite se partage strictement entre le frontend et le backend, et confondre les deux responsabilites cree des failles directes en production.

Ce qu'Angular protege nativement

Le moteur de template Angular echappe automatiquement les valeurs interpolees via {{ }} et les bindings de propriete comme [textContent]. Cela elimine la grande majorite des attaques XSS par injection de scripts dans le DOM.

Protection automatique : lorsque vous ecrivez {{ userInput }} dans un template Angular, la valeur est systematiquement echappee. Un attaquant injectant <script>alert(1)</script> verra son code affiche comme texte brut, pas execute.

Ce qu'Angular ne protege pas

Angular ne fait aucune validation d'autorisation metier. Il n'empeche pas un utilisateur connecte d'appeler directement une URL d'API avec les bons parametres. Il ne protege pas contre les injections SQL, les attaques SSRF ou les acces non autorises aux ressources d'autres utilisateurs (IDOR).

Regle d'or : le backend doit toujours re-valider chaque requete, peu importe ce que le frontend a verifie. Un attaquant peut totalement ignorer votre frontend et appeler directement votre API REST avec curl ou Postman.

Cartographie des menaces OWASP

Le tableau ci-dessous resume les 6 menaces les plus courantes dans les SPA Angular, en precisant ou chaque defense doit etre implementee.

Menace OWASP Gravite Role du frontend Angular Role du backend
XSS — Injection de script Critique Echappement automatique des bindings, eviter [innerHTML] Sanitization des sorties HTML, headers CSP
Broken Access Control (IDOR) Critique Guard UI (experience utilisateur uniquement) Verification d'autorisation sur chaque endpoint
Injection SQL / NoSQL Critique Aucun (le frontend n'accede pas a la BDD) Requetes parametrees, ORM, validation des entrees
Broken Authentication Eleve Interceptor, logout propre, expiration token Validation JWT, rotation des tokens, revocation
CSRF Eleve Eviter cookies persistants pour les tokens Token CSRF, validation Origin/Referer, SameSite
Exposition de donnees sensibles Eleve Ne pas logguer de donnees sensibles en console Chiffrement, masquage des champs, HTTPS obligatoire

Gestion securisee des tokens JWT

Le stockage des tokens d'authentification est l'un des sujets les plus debattus en securite SPA. Il n'existe pas de solution parfaite : chaque approche implique des compromis entre securite, commodite et complexite d'implementation.

Tableau comparatif des 3 approches de stockage

Stockage Accessible par JS Risque XSS Risque CSRF Recommande pour
localStorage Oui Critique Non A eviter pour les tokens sensibles
sessionStorage Oui Critique Non Sessions courtes et non critiques
Cookie HttpOnly Non Nul Possible (mitiger avec SameSite=Strict) Meilleure option avec backend cooperatif

Decoder un JWT sans librairie externe

Il est parfois utile de lire l'expiration d'un token cote client sans dependre d'une librairie tierce. Le payload d'un JWT est un objet JSON encode en Base64Url. Cette fonction utilitaire permet de le decoder et de verifier si le token est expire avant de declencher une requete API.

// utils/jwt.utils.ts

/**
 * Decode le payload d'un JWT sans verification de signature.
 * ATTENTION : ne jamais faire confiance aux donnees d'un JWT
 * sans validation cote serveur. Cette fonction sert uniquement
 * a lire l'expiration pour l'UX (redirection preventive).
 */
export function decodeJwtPayload(token: string): Record<string, unknown> | null {
  try {
    // Le payload est la 2eme partie du JWT (index 1) separee par des points
    const base64Url = token.split('.')[1];
    // Convertir Base64Url en Base64 standard puis decoder
    const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    return JSON.parse(atob(base64));
  } catch {
    return null;
  }
}

/**
 * Retourne true si le token est expire ou invalide.
 * La propriete `exp` dans le payload JWT est un timestamp Unix en secondes.
 */
export function isTokenExpired(token: string | null): boolean {
  if (!token) return true;
  const payload = decodeJwtPayload(token);
  if (!payload || typeof payload['exp'] !== 'number') return true;
  // Date.now() est en millisecondes, exp est en secondes
  return Date.now() >= (payload['exp'] as number) * 1000;
}

Duree de vie courte et refresh token

Quelle que soit l'approche de stockage, un access token doit avoir une duree de vie courte (5 a 15 minutes). Le refresh token peut vivre plus longtemps, mais doit etre stocke de facon securisee (cookie HttpOnly de preference) et revoque cote serveur lors du logout.

Strategie de logout securisee

Un logout fiable doit agir sur les deux extremites : cote client (vider les tokens) ET cote serveur (revoquer le refresh token). Se contenter du cote client laisse le refresh token valide jusqu'a son expiration naturelle.

// services/auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { Observable, map, tap } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class AuthService {
  private readonly TOKEN_KEY = 'access_token';
  private readonly REFRESH_KEY = 'refresh_token';

  constructor(private http: HttpClient, private router: Router) {}

  /** Recupere l'access token stocke en localStorage */
  getAccessToken(): string | null {
    return localStorage.getItem(this.TOKEN_KEY);
  }

  /** Appelle le backend pour obtenir un nouveau access token via le refresh token */
  refreshToken(): Observable<string> {
    const refreshToken = localStorage.getItem(this.REFRESH_KEY);
    return this.http
      .post<{ access_token: string }>('/api/auth/refresh', { refreshToken })
      .pipe(
        // Persister le nouveau access token et retourner sa valeur
        tap(res => localStorage.setItem(this.TOKEN_KEY, res.access_token)),
        map(res => res.access_token)
      );
  }

  /**
   * Logout complet (3 etapes) :
   *   1. Notifie le backend pour revoquer le refresh token en base
   *   2. Vide les tokens du stockage local
   *   3. Redirige vers la page de connexion
   */
  logout(): void {
    const refreshToken = localStorage.getItem(this.REFRESH_KEY);
    // Appel non bloquant : on logout meme si la requete echoue
    this.http.post('/api/auth/logout', { refreshToken }).subscribe();
    localStorage.removeItem(this.TOKEN_KEY);
    localStorage.removeItem(this.REFRESH_KEY);
    this.router.navigateByUrl('/login');
  }
}

Intercepteurs HTTP : auth et refresh token

Depuis Angular 15, les intercepteurs peuvent etre de simples fonctions (HttpInterceptorFn) au lieu de classes implementant HttpInterceptor. Ce style fonctionnel est plus leger, facilement testable et parfaitement compatible avec l'injection de dependances via inject().

Intercepteur complet avec retry automatique sur 401

Cet intercepteur couvre les trois besoins essentiels : ajout du header Authorization, gestion du refresh automatique sur les reponses 401, et possibilite de marquer certaines routes comme publiques pour ne pas leur attacher le token.

// interceptors/auth.interceptor.ts
import {
  HttpContextToken,
  HttpErrorResponse,
  HttpInterceptorFn,
  HttpContext
} from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { catchError, switchMap, throwError } from 'rxjs';
import { AuthService } from '../services/auth.service';

/**
 * HttpContextToken pour marquer les routes publiques.
 * Valeur par defaut : false (toutes les routes sont protegees).
 * Usage dans un service : withContext(new HttpContext().set(IS_PUBLIC, true))
 */
export const IS_PUBLIC = new HttpContextToken<boolean>(() => false);

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

  // ── 1. Ignorer les routes marquees comme publiques ──────────────────
  if (req.context.get(IS_PUBLIC)) {
    return next(req);
  }

  // ── 2. Ajouter le header Authorization si un token est disponible ──
  const token = auth.getAccessToken();
  const authReq = token
    ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
    : req;

  // ── 3. Intercepter les 401 pour tenter un refresh automatique ───────
  return next(authReq).pipe(
    catchError((err: HttpErrorResponse) => {
      // Eviter une boucle infinie si la route de refresh elle-meme renvoie 401
      if (err.status === 401 && !req.url.includes('/auth/refresh')) {
        return auth.refreshToken().pipe(
          switchMap(newToken => {
            // Retenter la requete originale avec le nouveau token
            const retryReq = req.clone({
              setHeaders: { Authorization: `Bearer ${newToken}` }
            });
            return next(retryReq);
          }),
          catchError(refreshErr => {
            // Le refresh a echoue → logout complet et redirection vers login
            auth.logout();
            router.navigate(['/login'], { state: { returnUrl: router.url } });
            return throwError(() => refreshErr);
          })
        );
      }
      // Propager toutes les autres erreurs sans interception
      return throwError(() => err);
    })
  );
};

Enregistrement dans le bootstrap de l'application

L'intercepteur doit etre enregistre via provideHttpClient(withInterceptors([...])). L'ordre dans le tableau definit l'ordre d'execution (le premier est le plus externe, comme un oignon).

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
import { authInterceptor } from './app/core/interceptors/auth.interceptor';

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    // authInterceptor sera execute sur toutes les requetes HttpClient
    provideHttpClient(withInterceptors([authInterceptor]))
  ]
}).catch(console.error);

Marquer une route comme publique avec HttpContextToken

Le IS_PUBLIC token permet d'indiquer a l'intercepteur de sauter l'ajout du header Authorization sur certaines requetes : authentification, assets publics, endpoints sans restriction, etc.

// services/auth-api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpContext } from '@angular/common/http';
import { IS_PUBLIC } from '../interceptors/auth.interceptor';

@Injectable({ providedIn: 'root' })
export class AuthApiService {
  constructor(private http: HttpClient) {}

  login(credentials: { email: string; password: string }) {
    return this.http.post('/api/auth/login', credentials, {
      // Marquer comme public : authInterceptor ne ajoutera pas de token
      context: new HttpContext().set(IS_PUBLIC, true)
    });
  }

  register(data: { email: string; password: string; name: string }) {
    return this.http.post('/api/auth/register', data, {
      context: new HttpContext().set(IS_PUBLIC, true)
    });
  }
}

Guards : protection des routes

Les guards Angular controlent l'acces aux routes de l'application. Ils sont essentiels pour l'experience utilisateur (eviter d'afficher un tableau de bord a un visiteur non connecte), mais ils ne remplacent jamais les verifications d'autorisation cote serveur.

Distinction fondamentale : un guard Angular est une barriere UX, pas une barriere de securite. N'importe quel developpeur peut contourner un guard en modifiant le JavaScript dans la console du navigateur. L'autorisation reelle doit etre validee par le backend sur chaque requete API.

Guard fonctionnel avec verification d'expiration

Depuis Angular 14, les guards peuvent etre de simples fonctions. Ce guard verifie non seulement la presence du token, mais aussi s'il est encore valide (non expire), evitant d'afficher une page qui genererait immediatement une erreur 401.

// guards/auth.guard.ts
import { CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';
import { isTokenExpired } from '../utils/jwt.utils';

/**
 * Guard qui protege les routes necessitant une authentification.
 * Verifie :
 *   1. La presence d'un token en localStorage
 *   2. Que le token n'est pas expire (verification cote client)
 * En cas d'echec, redirige vers /login avec l'URL originale en state
 * pour pouvoir y revenir apres connexion reussie.
 */
export const authGuard: CanActivateFn = (route, state) => {
  const router = inject(Router);
  const token = localStorage.getItem('access_token');

  if (!token || isTokenExpired(token)) {
    router.navigate(['/login'], {
      // Memoriser l'URL cible pour rediriger apres le login
      state: { returnUrl: state.url }
    });
    return false;
  }

  return true;
};

Revenir a l'URL d'origine apres connexion

Le composant de login peut recuperer l'URL de retour depuis le state de navigation, puis rediriger l'utilisateur vers sa destination initiale apres connexion reussie.

// components/login/login.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({ selector: 'app-login', templateUrl: './login.component.html' })
export class LoginComponent implements OnInit {
  // URL par defaut si aucune destination n'a ete memorisee
  private returnUrl = '/dashboard';

  constructor(private router: Router) {}

  ngOnInit(): void {
    // Recuperer l'URL de retour depuis le state de la navigation entrante
    const nav = this.router.getCurrentNavigation();
    const state = nav?.extras?.state as { returnUrl?: string } | undefined;
    if (state?.returnUrl) {
      this.returnUrl = state.returnUrl;
    }
  }

  onLoginSuccess(): void {
    // Rediriger vers l'URL memorisee au lieu du dashboard generique
    this.router.navigateByUrl(this.returnUrl);
  }
}

Guard de role pour l'autorisation par profil

Un guard peut aussi verifier le role de l'utilisateur encode dans le JWT. Ce pattern de guard en fabrique accepte une liste de roles autorises en parametre. Rappel : cette verification est purement UX — le backend doit imperativement verifier le role sur chaque endpoint protege.

// guards/role.guard.ts
import { CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';
import { decodeJwtPayload } from '../utils/jwt.utils';

/**
 * Fabrique de guard qui accepte un ou plusieurs roles autorises.
 * Exemple d'utilisation dans app.routes.ts :
 *   { path: 'admin', canActivate: [roleGuard('admin', 'superadmin')] }
 */
export function roleGuard(...allowedRoles: string[]): CanActivateFn {
  return (route, state) => {
    const router = inject(Router);
    const token = localStorage.getItem('access_token');

    if (!token) {
      router.navigate(['/login'], { state: { returnUrl: state.url } });
      return false;
    }

    const payload = decodeJwtPayload(token);
    const userRole = payload?.['role'] as string | undefined;

    if (!userRole || !allowedRoles.includes(userRole)) {
      // Token present mais role insuffisant → page acces refuse
      router.navigateByUrl('/forbidden');
      return false;
    }

    return true;
  };
}

XSS et DomSanitizer

Angular protege automatiquement les templates contre les attaques XSS pour la grande majorite des cas d'usage. Mais certains scenarios necessitent une vigilance particuliere : l'affichage de HTML dynamique, d'URLs calculees ou de styles inlines provenant de sources externes.

Ce qu'Angular echappe automatiquement

Tous les bindings de texte sont echappes par defaut. Il n'y a rien a faire de special pour se proteger dans les cas standards.

<!-- Angular echappe ces bindings automatiquement ──────────────────────── -->

<!-- Interpolation : le contenu est converti en entites HTML -->
<p>{{ userInput }}</p>

<!-- Property binding sur textContent : meme protection -->
<p [textContent]="userInput"></p>

<!-- Valeur d'input : echappee aussi -->
<input [value]="userInput" />

Les trois cas dangereux a surveiller

Trois contextes demandent une attention particuliere car Angular ne peut pas echapper leur contenu sans en changer la semantique voulue.

<!-- DANGER 1 : [innerHTML] interprete le HTML brut ────────────────────── -->
<!-- Si userHtml contient <script>, il sera execute dans certains contextes -->
<div [innerHTML]="userHtml"></div>

<!-- DANGER 2 : [href] peut contenir un vecteur javascript: ────────────── -->
<!-- Angular bloque javascript: mais certaines variantes encodees passent -->
<a [href]="dynamicUrl">Lien externe</a>

<!-- DANGER 3 : style dynamique avec URL externe ───────────────────────── -->
<div [style.backgroundImage]="'url(' + userUrl + ')'"></div>

DomSanitizer : quand c'est risque et quand c'est acceptable

DomSanitizer expose des methodes bypassSecurityTrust* qui desactivent explicitement les protections Angular. Ces methodes ne doivent etre utilisees que lorsque la source du contenu est absolument fiable et sous votre controle.

// MAUVAISE pratique : contourner la securite sur du contenu utilisateur ──
import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({
  template: '<div [innerHTML]="trustedHtml"></div>'
})
export class DangerousComponent {
  trustedHtml: SafeHtml;

  constructor(private sanitizer: DomSanitizer) {
    // NE JAMAIS faire cela avec du contenu soumis par un utilisateur
    this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(userProvidedHtml);
  }
}
// BONNE pratique : nettoyer d'abord avec DOMPurify ───────────────────────
// Installation : npm install dompurify && npm install -D @types/dompurify
import DOMPurify from 'dompurify';
import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({
  template: '<div [innerHTML]="safeHtml"></div>'
})
export class SafeCmsComponent {
  safeHtml: SafeHtml;

  constructor(private sanitizer: DomSanitizer) {
    // DOMPurify supprime les balises et attributs dangereux avant affichage
    const cleaned = DOMPurify.sanitize(cmsHtmlContent, {
      ALLOWED_TAGS: ['p', 'ul', 'ol', 'li', 'strong', 'em', 'a', 'h2', 'h3', 'code', 'pre'],
      ALLOWED_ATTR: ['href', 'target', 'rel']
    });
    // On peut maintenant marquer le contenu comme de confiance
    this.safeHtml = this.sanitizer.bypassSecurityTrustHtml(cleaned);
  }
}
  • Nettoyer le HTML cote serveur (recommande) ou via DOMPurify cote client.
  • Definir une liste blanche stricte des balises et attributs autorises.
  • Documenter chaque usage de bypassSecurityTrustHtml avec la justification dans un commentaire.
  • Mettre en place des tests de regression XSS sur les zones a risque (Playwright, Cypress).

Variables d'environnement et secrets

Le fichier environment.ts d'Angular est souvent mal utilise. Il est compile dans le bundle JavaScript final, ce qui signifie que tout ce qu'il contient est lisible par n'importe qui qui inspecte le code source de l'application en production.

Ce qui peut aller dans environment.ts

// environments/environment.prod.ts
// Ce fichier est remplace au moment du build par Angular CLI (fileReplacements)
export const environment = {
  production: true,

  // URL de l'API : visible dans le bundle, c'est acceptable
  apiUrl: 'https://api.monapp.com',

  // Feature flags : visibles mais non sensibles
  enableAnalytics: true,
  showDebugPanel: false,

  // Cle publique Stripe / Firebase : concues pour etre publiques.
  // La securite repose sur les regles cote serveur, pas sur la cle elle-meme.
  stripePublishableKey: 'pk_live_...'
};

Ce qui ne doit JAMAIS etre dans le bundle

Danger absolu : les secrets suivants ne doivent jamais apparaitre dans environment.ts ni dans aucun fichier TypeScript compile dans le bundle frontend. Ils seraient lisibles par tout le monde.
// INTERDIT dans environment.ts ──────────────────────────────────────────

// Cle privee API (Stripe, Twilio, SendGrid, AWS...)
stripeSecretKey: 'sk_live_...',         // INTERDIT — rester cote serveur

// Credentials de base de donnees
dbPassword: 'monpassword',              // INTERDIT

// JWT secret pour signer les tokens
jwtSecret: 'supersecretjwt',           // INTERDIT

// Cles d'API sans restriction de domaine (a proxifier via backend)
openaiApiKey: 'sk-...',                 // INTERDIT

// Chaine de connexion avec identifiants
mongoUri: 'mongodb+srv://user:pass@...' // INTERDIT

Variables d'environnement CI/CD

Pour les URLs et identifiants publics qui varient selon l'environnement (dev, staging, prod), la bonne pratique est de les injecter au moment du build via les variables d'environnement du systeme CI/CD.

# GitHub Actions — exemple d'injection de variables au build
- name: Build Angular
  run: ng build --configuration=production
  env:
    # Ces variables sont injectees via les Secrets GitHub Actions
    # et remplacent les placeholders dans environment.prod.ts
    API_URL: ${{ secrets.PROD_API_URL }}
    STRIPE_KEY: ${{ secrets.STRIPE_PUBLISHABLE_KEY }}

Content Security Policy (CSP)

Une CSP correctement configuree est le dernier rempart contre les attaques XSS qui auraient echappe aux autres mecanismes. Elle est configuree cote serveur (header HTTP) et indique au navigateur quelles sources de contenu sont autorisees.

# Exemple de header CSP pour une application Angular (Nginx)
# A placer dans la configuration du serveur web en production
add_header Content-Security-Policy "
  default-src 'self';
  script-src  'self';
  style-src   'self' 'unsafe-inline' https://fonts.googleapis.com;
  font-src    'self' https://fonts.gstatic.com;
  img-src     'self' data: https:;
  connect-src 'self' https://api.monapp.com;
  frame-ancestors 'none';
" always;
Note : 'unsafe-inline' pour les styles est souvent necessaire avec Angular (styles encapsules). En production avancee, privilegiez les nonces CSP pour eliminer aussi ce vecteur residuel.

CORS : ce qu'Angular fait (et ne fait pas)

Les erreurs CORS sont parmi les plus deroutantes pour les developpeurs Angular debutants. Il est important de comprendre que CORS est une politique du navigateur, pas une fonctionnalite d'Angular. Le framework ne peut ni l'activer ni le desactiver.

Principe fondamental : CORS (Cross-Origin Resource Sharing) est un mecanisme de securite des navigateurs qui bloque les requetes JavaScript vers un domaine different de celui de la page courante. C'est le serveur backend qui autorise ou refuse ces requetes en envoyant les bons headers HTTP en reponse. Angular n'a aucun role dans ce mecanisme.

Le proxy de developpement Angular CLI

En developpement, l'application Angular tourne sur localhost:4200 et l'API sur un port different ou une URL distante. Le serveur de dev Angular CLI peut faire office de proxy pour eviter les erreurs CORS localement, sans modifier le backend.

// proxy.conf.json — a placer a la racine du projet Angular
{
  "/api": {
    // Toutes les requetes vers /api seront proxifiees vers ce serveur
    "target": "http://localhost:3000",
    "secure": false,
    "changeOrigin": true,
    "logLevel": "debug"
  },
  "/auth": {
    "target": "https://api.monapp.com",
    "secure": true,
    "changeOrigin": true,
    // Recrire le prefixe du chemin si l'API distante utilise une autre base
    "pathRewrite": {
      "^/auth": "/v1/auth"
    }
  }
}
// angular.json — enregistrer le proxy dans la configuration de serve
{
  "projects": {
    "mon-app": {
      "architect": {
        "serve": {
          "options": {
            "proxyConfig": "proxy.conf.json"
          }
        }
      }
    }
  }
}
Attention : le proxy de developpement n'existe que localement. En production, c'est le serveur web (Nginx, Apache, Node) qui doit configurer les headers CORS. Ne jamais deployer proxy.conf.json comme solution de production.

Configuration CORS cote serveur (Nginx)

En production, les headers CORS doivent etre configures sur le serveur backend. Voici un exemple Nginx minimal pour une API REST consommee par une application Angular.

# nginx.conf — configuration CORS pour une API REST consommee par Angular
location /api {
    # Toujours specifier une origine precise en production (jamais *)
    add_header Access-Control-Allow-Origin  "https://www.monapp.com" always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
    add_header Access-Control-Allow-Credentials "true" always;

    # Repondre immediatement aux preflight OPTIONS sans passer au backend
    if ($request_method = OPTIONS) {
        return 204;
    }
}

Les erreurs CORS les plus frequentes

Erreur navigateur Cause probable Solution
No 'Access-Control-Allow-Origin' header Le backend n'envoie pas le header Configurer les headers CORS sur le serveur web ou le framework backend
Header not allowed in preflight Authorization absent de Allow-Headers Ajouter Authorization a la liste Access-Control-Allow-Headers
Credentials flag is 'true' but CORS header is '*' withCredentials: true avec Allow-Origin: * Specifier une origine precise et ajouter Access-Control-Allow-Credentials: true

Checklist securite production

Avant de deployer une application Angular en production, voici les points de securite a valider systematiquement. Cette checklist couvre le frontend, la configuration serveur et les bonnes pratiques de code.

Frontend Angular

  • Tous les tokens sont geres via un service dedie (pas directement dans les composants).
  • Un intercepteur centralise ajoute les headers d'authentification sur toutes les requetes protegees.
  • La gestion du 401 est implementee : refresh automatique ou logout + redirection.
  • Les routes sensibles sont protegees par des guards fonctionnels (CanActivateFn).
  • Aucun usage de [innerHTML] sans DOMPurify ou sanitization serveur.
  • Aucun secret (cle API privee, mot de passe, JWT secret) present dans environment.ts.
  • Le logout vide tous les tokens cote client ET envoie une requete de revocation au backend.
  • Les erreurs HTTP ne loggent pas de donnees sensibles en console (tokens, PII).

Configuration serveur et infrastructure

  • HTTPS obligatoire sur tous les endpoints avec redirection HTTP → HTTPS.
  • Headers de securite configures : CSP, X-Frame-Options, X-Content-Type-Options.
  • CORS restreint aux origines connues (jamais * en production avec credentials).
  • Cookies d'authentification avec HttpOnly, Secure et SameSite=Strict.
  • Access tokens avec une duree de vie courte (5 a 15 minutes maximum).
  • Dependances auditees regulierement avec npm audit et mises a jour d'Angular.

Tableau de synthese : menace → ou traiter → comment

Menace Ou traiter Comment
XSS Frontend + backend Bindings Angular + DOMPurify + header CSP
Acces non autorise (IDOR) Backend uniquement Verification d'autorisation sur chaque endpoint
Token vole via XSS Frontend + backend Cookie HttpOnly + rotation frequente + revocation
Session hijacking Backend + infrastructure HTTPS strict + tokens courte duree + fingerprinting
Secrets exposes dans le bundle Backend + CI/CD Variables d'env CI/CD, vault, revue de code obligatoire
CORS mal configure Backend Origines whitelistees, preflight correct, pas de wildcard
A retenir : la securite d'une application Angular n'est pas un etat final, c'est un processus continu. Auditez regulierement vos dependances (npm audit), maintenez Angular a jour et revoyez votre configuration CORS et CSP a chaque evolution de l'architecture.