Cache de jetons dans Microsoft. Identity.Web

La mise en cache des jetons améliore les performances, la fiabilité et l’expérience utilisateur des applications. Microsoft. Identity.Web fournit des stratégies de mise en cache flexibles qui équilibrent les performances, la persistance et la fiabilité opérationnelle.

Aperçu

Cette section décrit les jetons Microsoft. Identity.Web met en cache et pourquoi la mise en cache est importante pour votre application.

Quels jetons sont mis en cache ?

Microsoft. Identity.Web met en cache plusieurs types de jetons :

Type de jeton Taille Étendue Expulsion
Jetons d’accès ~2 kB Par (utilisateur/application, tenant, ressource) Automatique (basé sur la durée de vie)
Actualiser les jetons Variable Par compte d’utilisateur Manuel ou basé sur des stratégies
Jetons d’ID ~2 à 7 Ko Par utilisateur Automatique

Où la mise en cache des jetons s’applique :

Pourquoi les jetons de cache ?

Avantages en matière de performances :

  • Réduit les allers-retours à Microsoft Entra ID
  • Appels d’API plus rapides (L1 : <10 ms vs L2 : ~30 ms vs network : >100 ms)
  • Latence inférieure pour les utilisateurs finaux

Avantages de fiabilité :

  • Continue de travailler pendant les pannes temporaires de Microsoft Entra
  • Résilient aux transitoires de réseau
  • Dégradation progressive en cas d’échec du cache distribué

Avantages des coûts :

  • Réduit les demandes d’authentification (limitation de l’évitement)
  • Réduire les coûts de Azure pour les opérations d’authentification

Démarrage rapide

Commencez rapidement avec l’une des configurations de cache suivantes, en fonction de votre environnement.

Développement - Cache en mémoire

L’exemple suivant ajoute un cache de jetons en mémoire, adapté au développement et aux exemples :

using Microsoft.Identity.Web;

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

Avantages :

  • Simplicité de la configuration
  • Performances rapides
  • Aucune dépendance externe

Inconvénients:

  • Cache perdu lors du redémarrage de l’application. Dans une application web, les utilisateurs restent connectés via le cookie, mais doivent se reconnecter pour obtenir un jeton d’accès et remplir à nouveau le cache
  • Non adapté aux déploiements multiserveurs de production
  • Non partagé entre les instances d’application

Production - Cache distribué

Pour les applications de production, en particulier les déploiements multiserveurs, utilisez un cache distribué sauvegardé par Redis ou un autre fournisseur :

using Microsoft.Identity.Web;

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDistributedTokenCaches();

// Choose your cache implementation
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
    options.InstanceName = "MyApp_";
});

Avantages :

  • Résiste aux redémarrages de l'application
  • Partagé entre toutes les instances d’application
  • Mise en cache automatique L1+L2

Inconvénients:

  • Nécessite une infrastructure de cache externe
  • Complexité supplémentaire de la configuration
  • Latence réseau pour les opérations de cache

Choix d’une stratégie de cache

Utilisez l’organigramme de décision et la matrice suivants pour sélectionner la stratégie de cache qui correspond le mieux à votre déploiement.

flowchart TD
    Start([Token Caching<br/>Decision]) --> Q1{Production<br/>Environment?}

    Q1 -->|No - Dev/Test| DevChoice[In-Memory Cache<br/>AddInMemoryTokenCaches]
    Q1 -->|Yes| Q2{Multiple Server<br/>Instances?}

    Q2 -->|No - Single Server| Q3{App Restarts<br/>Acceptable?}
    Q3 -->|Yes| DevChoice
    Q3 -->|No| DistChoice

    Q2 -->|Yes| DistChoice[Distributed Cache<br/>AddDistributedTokenCaches]

    DistChoice --> Q4{Cache<br/>Implementation?}

    Q4 -->|High Performance| Redis[Redis Cache<br/>StackExchange.Redis<br/>⭐ Recommended]
    Q4 -->|Azure Native| Azure[Azure Cache for Redis,<br/>Azure Cosmos DB,<br/>or Azure Database for PostgreSQL]
    Q4 -->|On-Premises| SQL[SQL Server Cache<br/>AddDistributedSqlServerCache]
    Q4 -->|Testing| DistMem[Distributed Memory<br/>Not for production]

    Redis --> L1L2[Automatic L1+L2<br/>Caching]
    Azure --> L1L2
    SQL --> L1L2
    DistMem --> L1L2

    L1L2 --> Config[Configure Options<br/>MsalDistributedTokenCacheAdapterOptions]
    DevChoice --> MemConfig[Configure Memory Options<br/>MsalMemoryTokenCacheOptions]

    style Start fill:#e1f5ff
    style DevChoice fill:#d4edda
    style DistChoice fill:#fff3cd
    style Redis fill:#d1ecf1
    style L1L2 fill:#f8d7da

Matrice décisionnelle

Le tableau suivant récapitule les types de cache recommandés pour les scénarios de déploiement courants.

Scénario Cache recommandé Justification
Développement local In-Memory Simplicité, aucune infrastructure n’est nécessaire
Exemples/démonstrations In-Memory Configuration facile pour les démonstrations
Production à serveur unique (redémarre OK) In-Memory Acceptable si les sessions peuvent être rétablies
Production multiserveur Redis Cache partagé, hautes performances, fiable
applications hébergées Azure Azure Cache pour Redis Intégration Azure native, service managé
Entreprise locale SQL Server Tire parti de l’infrastructure existante
Environnements PostgreSQL PostgreSQL Utilise une base de données PostgreSQL existante, sémantique SQL familière
Environnements haute sécurité SQL Server + Chiffrement Résidence des données, chiffrement au repos
Test de scénarios distribués Mémoire distribuée Teste le comportement du cache L2 sans infrastructure

Implémentations du cache

Microsoft. Identity.Web prend en charge plusieurs implémentations de cache. Choisissez celui qui correspond à vos exigences d’infrastructure et de disponibilité.

Cache en mémoire

Quand les utiliser :

  • Développement et test
  • Déploiements à serveur unique avec comportement de redémarrage acceptable
  • Exemples et prototypes

Configuration:

Le code suivant inscrit le cache de jetons en mémoire avec les paramètres par défaut :

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

Avec les options personnalisées :

Vous pouvez personnaliser les limites d’expiration et de taille en passant des options :

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches(options =>
    {
        // Token cache entry will expire after this duration
        options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);

        // Limit cache size (default is unlimited)
        options.SizeLimit = 500 * 1024 * 1024; // 500 MB
    });

→ En savoir plus sur la configuration du cache en mémoire


Cache distribué (L2) avec prise en charge automatique de L1

Quand les utiliser :

  • Déploiements multiserveurs de production
  • Applications nécessitant une persistance du cache entre les redémarrages
  • Scénarios de haute disponibilité

Caractéristique clé : Depuis Microsoft.Identity.Web v1.8.0, le cache distribué inclut automatiquement un cache L1 en mémoire pour les performances et la fiabilité.

Ajoutez le chaîne de connexion Redis à appsettings.json :

{
  "ConnectionStrings": {
    "Redis": "localhost:6379"
  }
}

Inscrivez ensuite le cache de jetons distribué et le fournisseur Redis dans Program.cs :

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.TokenCacheProviders.Distributed;

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDistributedTokenCaches();

// Redis cache implementation
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
    options.InstanceName = "MyApp_"; // Unique prefix per application
});

// Optional: Configure distributed cache behavior
builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
    // Control L1 cache size
    options.L1CacheOptions.SizeLimit = 500 * 1024 * 1024; // 500 MB

    // Handle L2 cache failures gracefully
    options.OnL2CacheFailure = (exception) =>
    {
        if (exception is StackExchange.Redis.RedisConnectionException)
        {
            // Log the failure
            // Optionally attempt reconnection
            return true; // Retry the operation
        }
        return false; // Don't retry
    };
});

Azure Cache pour Redis

Pour utiliser Azure Cache pour Redis, inscrivez le cache auprès de votre Azure chaîne de connexion :

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("AzureRedis");
    options.InstanceName = "MyApp_";
});

Format de la chaîne de connexion :

<cache-name>.redis.cache.windows.net:6380,password=<access-key>,ssl=True,abortConnect=False

cache de SQL Server

L’exemple suivant configure SQL Server en tant que back-end de cache distribué :

builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString("TokenCacheDb");
    options.SchemaName = "dbo";
    options.TableName = "TokenCache";

    // Set expiration longer than access token lifetime (default 1 hour)
    // This prevents cache entries from expiring before tokens
    options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});

cache Azure Cosmos DB

L’exemple suivant configure Azure Cosmos DB en tant que back-end de cache distribué :

builder.Services.AddCosmosCache((CosmosCacheOptions options) =>
{
    options.ContainerName = builder.Configuration["CosmosCache:ContainerName"];
    options.DatabaseName = builder.Configuration["CosmosCache:DatabaseName"];
    options.ClientBuilder = new CosmosClientBuilder(
        builder.Configuration["CosmosCache:ConnectionString"]);
    options.CreateIfNotExists = true;
});

Cache PostgreSQL

Nécessite le package NuGet Microsoft.Extensions.Caching.Postgres.

appsettings.json:

{
  "ConnectionStrings": {
    "PostgresCache": "Host=localhost;Database=mydb;Username=myuser;Password=mypassword"
  },
  "PostgresCache": {
    "SchemaName": "public",
    "TableName": "token_cache",
    "CreateIfNotExists": true
  }
}

Inscrivez ensuite le cache PostgreSQL dans Program.cs :

builder.Services.AddDistributedPostgresCache(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString("PostgresCache");
    options.SchemaName = builder.Configuration["PostgresCache:SchemaName"];
    options.TableName = builder.Configuration["PostgresCache:TableName"];
    options.CreateIfNotExists = builder.Configuration.GetValue<bool>("PostgresCache:CreateIfNotExists");
    options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});

→ En savoir plus sur la configuration du cache distribué


Avertissement

La mise en cache basée sur une session présente des limitations significatives. Utilisez plutôt un cache distribué.

L’exemple suivant montre la mise en cache de jetons basée sur une session pour référence :

using Microsoft.Identity.Web.TokenCacheProviders.Session;

// In Program.cs
builder.Services.AddSession();

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddSessionTokenCaches();

// In middleware pipeline
app.UseSession(); // Must be before UseAuthentication()
app.UseAuthentication();
app.UseAuthorization();

Limitations :

  • Problèmes de taille des cookies - Les jetons d’ID volumineux avec de nombreuses revendications provoquent des problèmes
  • Conflits de périmètre Scope - Impossible à utiliser avec un singleton TokenAcquisition (par exemple, Microsoft Graph SDK)
  • Affinité de session requise - Ne fonctionne pas correctement dans les scénarios à charge équilibrée
  • Non recommandé - Utiliser le cache distribué à la place

Configuration avancée

Ces options vous permettent d’affiner le comportement du cache pour les stratégies de performances, de sécurité et d’éviction.

Contrôle de cache L1

Le cache L1 (en mémoire) améliore les performances lorsque vous utilisez des caches distribués. Le code suivant configure la taille et le comportement du cache L1 :

builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
    // Control L1 cache size (default: 500 MB)
    options.L1CacheOptions.SizeLimit = 100 * 1024 * 1024; // 100 MB

    // Disable L1 cache if session affinity is not available
    // (forces all requests to use L2 cache for consistency)
    options.DisableL1Cache = false;
});

Quand désactiver L1 :

  • Aucune affinité de session dans l’équilibreur de charge
  • Les utilisateurs fréquemment invités à entrer l’authentification multifacteur en raison de l’incohérence du cache
  • Compromis : l’accès L2 est plus lent (~30 ms contre ~10 ms)

Stratégies d’éviction du cache

Les stratégies d’éviction contrôlent quand les jetons mis en cache sont supprimés. Le code suivant définit l’expiration absolue et glissante :

builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
    // Absolute expiration (removed after this time, regardless of use)
    options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(72);

    // Sliding expiration (renewed on each access)
    options.SlidingExpiration = TimeSpan.FromHours(2);
});

Vous pouvez également configurer l’éviction via appsettings.json:

{
  "TokenCacheOptions": {
    "AbsoluteExpirationRelativeToNow": "72:00:00",
    "SlidingExpiration": "02:00:00"
  }
}
builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(
    builder.Configuration.GetSection("TokenCacheOptions"));

Recommendations:

  • Définir l’expiration plus longue que la durée de vie des jetons (les jetons expirent généralement en 1 heure)
  • Valeur par défaut : expiration glissante de 90 minutes
  • Équilibre entre l’utilisation de la mémoire et l’expérience utilisateur
  • Considérez : 72 heures absolues + 2 heures glissantes pour une expérience utilisateur correcte

→ En savoir plus sur les stratégies d’éviction du cache


Chiffrement au repos

Pour protéger les données de jeton sensibles dans les caches distribués, activez le chiffrement via ASP.NET Core Protection des données.

Une machine

Sur un seul ordinateur, activez le chiffrement avec le fournisseur de protection des données intégré :

builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
    options.Encrypt = true; // Uses ASP.NET Core Data Protection
});

Systèmes distribués (plusieurs serveurs)

Important

Les systèmes distribués ne partagent pas les clés de chiffrement par défaut. Vous devez configurer le partage de clés :

Azure Key Vault (recommandé) :

Le code suivant conserve les clés pour Stockage Blob Azure et les protège avec Azure Key Vault :

using Microsoft.AspNetCore.DataProtection;

builder.Services.AddDataProtection()
    .PersistKeysToAzureBlobStorage(new Uri(builder.Configuration["DataProtection:BlobUri"]))
    .ProtectKeysWithAzureKeyVault(
        new Uri(builder.Configuration["DataProtection:KeyIdentifier"]),
        new DefaultAzureCredential());

Basé sur un certificat :

Le code suivant conserve les clés d’un partage de fichiers et les protège avec un certificat X.509 :

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\keys"))
    .ProtectKeysWithCertificate(
        new X509Certificate2("current.pfx", builder.Configuration["CertPassword"]))
    .UnprotectKeysWithAnyCertificate(
        new X509Certificate2("current.pfx", builder.Configuration["CertPassword"]),
        new X509Certificate2("previous.pfx", builder.Configuration["PrevCertPassword"]));

→ En savoir plus sur le chiffrement et la protection des données


Considérations relatives aux performances du cache

Utilisez les estimations suivantes pour planifier la capacité de cache pour votre application.

Estimations de taille de jeton

Type de jeton Taille standard Par Remarques
Jetons d’application ~2 kB Locataire × Ressource Évincé automatiquement
Jetons utilisateur ~7 Ko Utilisateur × Locataire × Ressource Éviction manuelle nécessaire
Jetons d’actualisation Variable Utilisateur Durée de vie longue

Planification de la mémoire

Pour 500 utilisateurs simultanés appelant 3 API :

  • Jetons utilisateur : 500 × 3 × 7 Ko = 10,5 Mo
  • Avec surcharge : ~15-20 Mo

Pour 10 000 utilisateurs simultanés :

  • Jetons utilisateur : 10 000 × 3 × 7 Ko = 210 Mo
  • Avec surcharge : ~300-350 Mo

Recommandation: Définissez la limite de taille du cache L1 en fonction des utilisateurs simultanés attendus.

Bonnes pratiques

Suivez ces instructions pour garantir la mise en cache des jetons fiable et efficace.

Utiliser le cache distribué en production - Essentiel pour les déploiements multi-serveurs

Définir les limites de taille de cache appropriées - Empêcher la croissance de la mémoire illimitée

Configurer des stratégies d’éviction - Équilibrer l’utilisation de l’expérience utilisateur et de la mémoire

Activer le chiffrement pour les données sensibles - Protéger les jetons au repos

Surveiller l’intégrité du cache - Suivre les taux d’accès, les échecs et les performances

Gérer correctement les défaillances du cache L2 - Le cache L1 garantit la résilience

Tester le comportement du cache - Vérifier les scénarios de redémarrage et le basculement

N’utilisez pas le cache de mémoire distribuée en production - Non persistant ou distribué

N’utilisez pas le cache de session - Présente des limitations significatives

Ne définissez pas l’expiration plus courte que la durée de vie du jeton - Force la réauthentification inutile

N’oubliez pas le partage de clés de chiffrement : les systèmes distribués ont besoin de clés partagées