AngularASP.NET CoreCSPSécurité.NETFrontend

Configurez une CSP stricte entre Angular et ASP.NET Core, débloquez les styles inline avec un nonce et mesurez le compromis de sécurité d’unsafe-inline.

Yva Hajatiana
15 septembre 2026
9 min de lecture
Partager :X / TwitterLinkedIn
Flux de nonce CSP entre ASP.NET Core, le navigateur et Angular

CSP Angular et backend .NET : gérer les styles inline

Une application Angular peut fonctionner parfaitement en développement, puis perdre sa mise en page dès qu’un backend ASP.NET Core ajoute une Content Security Policy stricte. La console affiche alors un message du type Refused to apply inline style et certains composants deviennent partiellement ou totalement invisibles.

Le problème ne vient pas nécessairement d’une mauvaise feuille CSS. Angular insère des éléments <style> au moment où des composants sont rendus. Une bibliothèque d’interface peut faire de même. Si la directive style-src n’autorise que 'self', le navigateur accepte les fichiers CSS du même domaine, mais refuse ces styles créés dans la page.

La bonne solution n’est pas de supprimer immédiatement la CSP. Il faut comprendre quel style est bloqué, transmettre à Angular un nonce imprévisible généré pour chaque réponse HTML et n’élargir la politique qu’en connaissance de cause.

Pourquoi Angular déclenche une erreur de style inline

Une CSP est une règle appliquée par le navigateur. Avec cette politique :

Content-Security-Policy: default-src 'self'; style-src 'self'; script-src 'self'

le navigateur peut charger styles.css depuis l’origine de l’application. En revanche, il bloque par défaut :

  • un bloc <style>...</style> sans nonce ni hash autorisé ;
  • un attribut HTML comme <div style="width: 50%"> lorsque la politique couvre les attributs inline ;
  • les styles injectés par une bibliothèque qui ne connaît pas le nonce ;
  • les ressources CSS, polices ou images provenant d’un domaine absent de la politique.

Angular encapsule souvent les styles des composants et les ajoute au document sous forme d’éléments <style>. Une CSP stricte doit donc reconnaître ces éléments comme légitimes sans autoriser tous les styles inline du site.

À retenir. La CSP est appliquée après Angular, au niveau du navigateur. DomSanitizer.bypassSecurityTrustStyle() modifie la décision de sanitation d’Angular, mais ne contourne pas une interdiction CSP.

Le nonce, compromis recommandé entre CSP et Angular

Un nonce est une valeur aléatoire utilisée une seule fois. Le serveur l’ajoute à la politique :

Content-Security-Policy: style-src 'self' 'nonce-K7q...';

Angular applique exactement la même valeur aux éléments <style> qu’il crée :

<style nonce="K7q...">/* styles du composant */</style>

Le navigateur accepte ce bloc et continue de refuser un style injecté sans la bonne valeur. Le nonce ne doit contenir ni le préfixe nonce- dans l’attribut HTML, ni une valeur fixe réutilisée entre les visiteurs.

ASP.NET Core génère un nonce par réponse, l’insère dans l’en-tête CSP et le transmet à Angular afin d’autoriser uniquement les styles qui le portent
La valeur du nonce doit être identique dans l’en-tête et dans le document, mais différente pour chaque réponse HTML.

Angular propose trois mécanismes officiels :

  1. activer security.autoCsp dans la configuration du workspace ;
  2. poser ngCspNonce sur l’élément racine de l’application ;
  3. fournir le token d’injection CSP_NONCE au démarrage.

Le choix dépend surtout de la manière dont l’application est construite et servie.

Solution avec ngCspNonce et ASP.NET Core

Lorsque ASP.NET Core sert le fichier index.html, le backend peut générer un nonce cryptographiquement aléatoire, écrire l’en-tête CSP et remplacer un marqueur dans le document.

Dans le fichier HTML source :

<app-root ngCspNonce="__CSP_NONCE__"></app-root>

Un exemple minimal côté ASP.NET Core :

using System.Security.Cryptography;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseWhen(
    context => !string.Equals(
        context.Request.Path.Value,
        "/index.html",
        StringComparison.OrdinalIgnoreCase),
    branch => branch.UseStaticFiles());

async Task ServeIndexAsync(HttpContext context)
{
    var nonce = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));

    context.Response.Headers.ContentSecurityPolicy = string.Join(" ",
        "default-src 'self';",
        "base-uri 'self';",
        "object-src 'none';",
        "frame-ancestors 'none';",
        $"style-src 'self' 'nonce-{nonce}';",
        "font-src 'self';",
        "img-src 'self' data:;",
        "connect-src 'self';",
        "script-src 'self';");

    var indexPath = Path.Combine(app.Environment.WebRootPath, "index.html");
    var html = await File.ReadAllTextAsync(indexPath, context.RequestAborted);
    html = html.Replace("__CSP_NONCE__", nonce, StringComparison.Ordinal);

    context.Response.ContentType = "text/html; charset=utf-8";
    await context.Response.WriteAsync(html, context.RequestAborted);
}

app.MapGet("/index.html", ServeIndexAsync);
app.MapFallback(ServeIndexAsync);

app.Run();

UseWhen exclut explicitement /index.html du middleware de fichiers statiques. La route directe et les routes de la SPA passent ainsi toutes par ServeIndexAsync : aucune ne peut renvoyer le marqueur __CSP_NONCE__ sans l’en-tête CSP correspondant.

Cet exemple illustre le mécanisme, pas une politique universelle. Il faut ajouter à connect-src, img-src ou font-src les origines réellement utilisées par l’application, et uniquement celles-ci.

Il faut aussi traiter le cache avec attention. Une réponse HTML mise en cache par un CDN avec un nonce généré à l’origine peut exposer la même valeur à de nombreux visiteurs. Le nonce devrait être créé au plus près de la livraison, idéalement à l’edge, ou la réponse HTML dynamique ne devrait pas être partagée. Les bundles JavaScript et CSS portant un nom avec hash peuvent rester mis en cache longtemps.

Utiliser CSP_NONCE quand le HTML doit rester cacheable

Si la valeur est disponible au démarrage sans modifier l’élément racine, Angular expose le token CSP_NONCE :

import { CSP_NONCE } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';

import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: CSP_NONCE,
      useValue: globalThis.__cspNonce,
    },
  ],
});

Cette valeur doit encore parvenir au navigateur par un canal cohérent avec la CSP. Elle ne doit jamais être récupérée depuis une API après le bootstrap : Angular en a besoin lorsqu’il crée ses premiers styles.

La documentation Angular précise également que CSP_NONCE n’est pas la bonne option lorsque le build inline le CSS critique. Dans ce cas, préférez autoCsp ou ngCspNonce, ou désactivez l’inlining critique si votre chaîne de livraison ne sait pas appliquer le nonce aux éléments produits dans le HTML initial.

Dans angular.json, une configuration moderne peut activer le mécanisme automatique :

{
  "projects": {
    "front": {
      "architect": {
        "build": {
          "options": {
            "security": {
              "autoCsp": true
            }
          }
        }
      }
    }
  }
}

Vérifiez que la version du builder Angular utilisée reconnaît cette option. Une configuration copiée dans un ancien projet peut être ignorée ou rejetée par le schéma.

Le bypass unsafe-inline : utile, mais coûteux

Le déblocage le plus rapide consiste à ajouter 'unsafe-inline' :

Content-Security-Policy: style-src 'self' 'unsafe-inline';

L’application retrouve généralement sa mise en page, mais la politique autorise alors tous les styles inline couverts par style-src. La CSP conserve une partie de sa valeur, notamment si script-src reste stricte, mais elle ne distingue plus les styles produits par Angular de ceux qu’un contenu injecté tenterait d’ajouter.

Ce compromis peut être temporairement acceptable lorsque :

  • une bibliothèque indispensable ne sait pas propager un nonce ;
  • l’équipe déploie d’abord la CSP en mode rapport ;
  • la migration de nombreux attributs style ne peut pas être atomique ;
  • le risque résiduel est documenté et compensé par une sanitation stricte.

Ce ne devrait pas être la première réponse à chaque violation. Commencez par identifier la directive effective dans le message du navigateur : style-src-elem concerne les balises <style> et les feuilles liées, tandis que style-src-attr concerne les attributs style. Un nonce autorise les éléments <style>, pas les attributs HTML individuels.

Avec CSP niveau 3, une politique peut séparer les deux surfaces :

Content-Security-Policy:
  style-src-elem 'self' 'nonce-K7q...';
  style-src-attr 'none';

Cette séparation permet d’accepter les feuilles et styles Angular portant le nonce tout en interdisant les attributs inline écrits dans le HTML. Testez toutefois tous les navigateurs supportés et les composants tiers avant de l’imposer.

Pourquoi les hashes deviennent fragiles avec Angular

Une autre possibilité consiste à autoriser le hash SHA-256 exact d’un bloc inline. Elle convient à un petit style statique dont le contenu ne change jamais. Elle devient pénible pour les styles générés par un framework : le moindre espace, changement de build ou mise à jour de bibliothèque modifie le hash.

Les hashes sont donc adaptés à quelques blocs immuables. Les nonces conviennent mieux aux styles dynamiques connus du serveur. 'unsafe-inline' reste un mécanisme de compatibilité, pas une équivalence au nonce.

Les bibliothèques tierces restent un point de blocage

Transmettre un nonce à Angular ne garantit pas que tous les composants externes l’utiliseront. Un éditeur riche, une bibliothèque de graphiques ou un système de thèmes peut créer lui-même une balise <style> avec document.createElement.

Dans ce cas, l’ordre de préférence est le suivant :

  1. utiliser l’option nonce officielle de la bibliothèque ;
  2. charger ses styles depuis un fichier CSS autorisé ;
  3. remplacer la bibliothèque ou isoler la fonctionnalité ;
  4. ouvrir temporairement style-src avec un risque explicitement accepté.

N’ajoutez pas automatiquement angular#unsafe-bypass à trusted-types. Cette politique Trusted Types concerne les appels DomSanitizer.bypassSecurityTrust…, pas le chargement des styles par CSP. Les deux protections sont complémentaires, mais elles ne résolvent pas le même problème.

Déployer sans casser l’application

Une migration progressive réduit les surprises :

  1. envoyer d’abord Content-Security-Policy-Report-Only ;
  2. collecter les violations sur les parcours réels et les tests end-to-end ;
  3. classer les erreurs par effective-directive et par source ;
  4. ajouter le nonce Angular et configurer les bibliothèques compatibles ;
  5. remplacer les attributs inline persistants par des classes CSS ;
  6. passer en mode bloquant, puis resserrer les origines.

Le rapport ne doit pas contenir de données sensibles et son endpoint doit résister au volume. Une extension de navigateur, un proxy d’entreprise ou un script injecté localement peut aussi produire des violations qui ne viennent pas de votre code.

Checklist de production

  • le nonce contient au moins 128 bits d’aléa et change à chaque réponse HTML ;
  • la valeur de l’en-tête correspond à celle transmise à Angular ;
  • le HTML partagé par un CDN ne réutilise pas un nonce d’origine ;
  • object-src 'none', base-uri et frame-ancestors sont explicitement définis ;
  • les domaines de connect-src, font-src et img-src sont minimaux ;
  • les composants tiers sont testés sans 'unsafe-inline' ;
  • la CSP est couverte par des tests de navigateur, pas seulement par un test HTTP ;
  • Trusted Types est évalué séparément pour renforcer la protection XSS.

Le compromis raisonnable

La CSP ne doit pas empêcher Angular de fonctionner, mais les exigences d’Angular ne justifient pas de désactiver toute protection inline. Pour une application servie par ASP.NET Core, le meilleur équilibre est généralement un nonce imprévisible par réponse, transmis avec ngCspNonce ou géré par autoCsp.

'unsafe-inline' peut servir de pont pendant une migration ou pour une dépendance récalcitrante. Il faut alors le considérer comme une réduction explicite de la défense en profondeur, la mesurer et préparer sa suppression. Le vrai objectif n’est pas une CSP théoriquement parfaite : c’est une politique testée, compatible avec l’application et assez stricte pour bloquer ce que l’équipe n’a pas choisi d’exécuter.

Ressources officielles

Mots-clés :AngularASP.NET CoreCSPSécurité.NETFrontend
Y

Yva Hajatiana

Articles techniques sur l'ingénierie logicielle, .NET, le cloud et l'intelligence artificielle appliquée aux applications.