Caché de tokens en Microsoft. Identity.Web

El almacenamiento en caché de tokens mejora el rendimiento, la confiabilidad y la experiencia del usuario de las aplicaciones. Microsoft. Identity.Web proporciona estrategias de almacenamiento en caché flexibles que equilibran el rendimiento, la persistencia y la confiabilidad operativa.

Visión general

En esta sección se describen los tokens que Microsoft.Identity.Web almacena en caché y por qué el almacenamiento en caché es importante para tu aplicación.

¿Qué tokens se almacenan en caché?

Microsoft. Identity.Web almacena en caché varios tipos de tokens:

Tipo de token Tamaño Ámbito Expulsión
Tokens de acceso ~2 KB Por (usuario/aplicación, entidad, recurso) Automático (basado en la vida útil)
Tokens de renovación Variable Por cuenta de usuario Basado en directivas o manuales
Tokens de identificador ~2-7 KB Por usuario Automático

Donde se aplica el almacenamiento en caché de tokens:

¿Por qué los tokens de caché?

Ventajas de rendimiento:

  • Reduce los viajes de ida y vuelta a Microsoft Entra ID
  • Llamadas API más rápidas (L1: <10 ms frente a L2: ~30 ms frente a red: >100 ms)
  • Menor latencia para los usuarios finales

Ventajas de confiabilidad:

  • Continúa trabajando durante interrupciones temporales de Microsoft Entra
  • Resistente a los transitorios de red
  • Degradación gradual cuando falla la caché distribuida

Ventajas de costos:

  • Reduce las solicitudes de autenticación (prevención de la limitación de frecuencia)
  • Menores costos de Azure para las operaciones de autenticación

Inicio rápido

Empiece a trabajar rápidamente con una de las siguientes configuraciones de caché, en función de su entorno.

Desarrollo: caché en memoria

En el ejemplo siguiente se agrega una caché de tokens en memoria, adecuada para el desarrollo y los ejemplos:

using Microsoft.Identity.Web;

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

Ventajas:

  • Instalación simple
  • Rendimiento rápido
  • Sin dependencias externas

Desventajas:

  • Caché perdida en el reinicio de la aplicación. En una aplicación web, los usuarios permanecen conectados a través de la cookie, pero deben volver a iniciar sesión para obtener un token de acceso y volver a rellenar la memoria caché.
  • No es adecuado para implementaciones de varios servidores de producción
  • No se comparte entre instancias de aplicación

Producción: caché distribuida

En el caso de las aplicaciones de producción, especialmente las implementaciones de varios servidores, use una caché distribuida respaldada por Redis u otro proveedor:

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

Ventajas:

  • Sobrevive a los reinicios de la aplicación
  • Compartido en todas las instancias de aplicación
  • Almacenamiento en caché automático L1+L2

Desventajas:

  • Requiere infraestructura de caché externa
  • Complejidad adicional de la configuración
  • Latencia de red para las operaciones de caché

Elección de una estrategia de caché

Use el siguiente diagrama de flujo de decisión y matriz para seleccionar la estrategia de caché que mejor se adapte a la implementación.

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

Matriz de decisión

En la tabla siguiente se resumen los tipos de caché recomendados para escenarios de implementación comunes.

Escenario Caché recomendada Justificación
Desarrollo local In-Memory Simplicidad, sin necesidad de infraestructura
Ejemplos y demostraciones In-Memory Configuración sencilla para demostraciones
Producción de un solo servidor (reinicia Ok) In-Memory Aceptable si se pueden restablecer las sesiones
Producción en servidores múltiples Redis Caché compartida, alto rendimiento, confiable
Aplicaciones hospedadas en Azure Azure Cache for Redis Integración de Azure nativa, servicio administrado
Empresa local SQL Server Aprovecha la infraestructura existente
Entornos de PostgreSQL PostgreSQL Usa una base de datos PostgreSQL existente, semántica de SQL conocida.
Entornos de alta seguridad SQL Server + Cifrado Residencia de los datos, cifrado en reposo
Probar escenarios distribuidos Memoria distribuida Prueba el comportamiento de la caché L2 sin infraestructura

Implementaciones de caché

Microsoft. Identity.Web admite varias implementaciones de caché. Elija la que coincida con los requisitos de infraestructura y disponibilidad.

Caché en memoria

Cuándo usar:

  • Desarrollo y pruebas
  • Implementaciones de servidor único con un comportamiento de reinicio aceptable
  • Ejemplos y prototipos

Configuration:

El código siguiente registra la caché de tokens en memoria con la configuración predeterminada:

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

Con opciones personalizadas:

Puede personalizar los límites de expiración y tamaño pasando las opciones:

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

→ Más información sobre la configuración de caché en memoria


Caché distribuida (L2) con compatibilidad automática con L1

Cuándo usar:

  • Implementaciones de varios servidores de producción
  • Aplicaciones que requieren persistencia de caché entre reinicios
  • Escenarios de alta disponibilidad

Característica clave: Desde Microsoft.Identity.Web v1.8.0, la caché distribuida incluye automáticamente una caché de nivel 1 en memoria para mejorar el rendimiento y la confiabilidad.

Agregue la cadena de conexión de Redis a appsettings.json:

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

A continuación, registre la caché de tokens distribuida y el proveedor de Redis en 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 for Redis

Para usar Azure Cache for Redis, registre su memoria caché con su cadena de conexión de Azure.

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

Formato de la cadena de conexión:

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

caché de SQL Server

En el ejemplo siguiente se configura SQL Server como back-end de caché distribuida:

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

caché de Azure Cosmos DB

En el ejemplo siguiente se configura Azure Cosmos DB como back-end de caché distribuida:

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

Caché de PostgreSQL

Requiere el paquete 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
  }
}

A continuación, registre la memoria caché de PostgreSQL en 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);
});

→ Más información sobre la configuración de caché distribuida


Precaución

El almacenamiento en caché basado en sesión tiene limitaciones significativas. Use una caché distribuida en su lugar.

En el ejemplo siguiente se muestra el almacenamiento en caché de tokens basado en sesión como referencia:

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

Limitaciones:

  • Problemas de tamaño de cookies - Los tokens de ID grandes con muchas reclamaciones provocan problemas
  • Conflictos de Scope: no se puede usar con singleton TokenAcquisition (por ejemplo, SDK de Microsoft Graph)
  • Afinidad de sesión necesaria : no funciona bien en escenarios de carga equilibrada
  • No recomendado : use la caché distribuida en su lugar.

Configuración avanzada

Estas opciones permiten ajustar el comportamiento de la caché para las directivas de rendimiento, seguridad y expulsión.

Control de caché L1

La caché L1 (en memoria) mejora el rendimiento cuando se usan cachés distribuidas. El código siguiente configura el tamaño y el comportamiento de la caché 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;
});

Cuándo deshabilitar L1:

  • No hay afinidad de sesión en el equilibrador de carga
  • A los usuarios se les solicita con frecuencia MFA debido a la incoherencia de caché
  • Compensación: el acceso L2 es más lento, de alrededor de 30 ms frente a 10 ms.

Directivas de expulsión de caché

Las directivas de expulsión controlan cuándo se quitan los tokens almacenados en caché. El código siguiente establece la expiración absoluta y deslizante:

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

También puede configurar la expulsión a través de appsettings.json:

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

Recomendaciones:

  • Establecer la expiración más larga que la duración del token (los tokens suelen expirar en 1 hora)
  • Valor predeterminado: expiración deslizante de 90 minutos
  • Equilibrio entre el uso de memoria y la experiencia del usuario
  • Considere: 72 horas absolutas + 2 horas flexibles para una buena experiencia de usuario

→ Más información sobre las estrategias de expulsión de caché


Cifrado en reposo

Para proteger los datos confidenciales de los tokens en cachés distribuidos, habilite el cifrado a través de Protección de datos de ASP.NET Core.

Una máquina

En una sola máquina, habilite el cifrado con el proveedor de protección de datos integrado:

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

Sistemas distribuidos (varios servidores)

Importante

Los sistemas distribuidos no comparten claves de cifrado de forma predeterminada. Debe configurar el uso compartido de claves:

Azure Key Vault (recomendado):

El código siguiente conserva las claves para Azure Blob Storage y las protege con 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());

Basado en certificados:

El código siguiente conserva las claves en un recurso compartido de archivos y las protege con un certificado 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"]));

→ Más información sobre el cifrado y la protección de datos


Consideraciones sobre el rendimiento de la memoria caché

Use las estimaciones siguientes para planear la capacidad de caché de la aplicación.

Estimaciones del tamaño del token

Tipo de token Tamaño típico Por Notas
Tokens de aplicación ~2 KB Inquilino × Recurso Eliminado automáticamente
Tokens de usuario ~7 KB Usuario × Inquilino × Recurso Expulsión manual necesaria
Tokens de actualización Variable Usuario Larga duración

Planeación de memoria

Para 500 usuarios simultáneos que llaman a 3 API:

  • Tokens de usuario: 500 × 3 × 7 KB = 10,5 MB
  • Con sobrecarga: ~15-20 MB

Para 10 000 usuarios simultáneos:

  • Tokens de usuario: 10 000 × 3 × 7 KB = 210 MB
  • Con sobrecarga: ~300-350 MB

Recomendación: Establezca el límite de tamaño de caché L1 en función de los usuarios simultáneos esperados.

procedimientos recomendados

Siga estas instrucciones para garantizar el almacenamiento en caché de tokens confiable y eficaz.

Uso de la caché distribuida en producción : esencial para implementaciones de varios servidores

Establecer límites adecuados del tamaño del caché – evitar el crecimiento sin límites de la memoria

Configurar políticas de expulsión - equilibrar el uso de la experiencia de usuario y de la memoria

Habilitación del cifrado para datos confidenciales : protección de tokens en reposo

Supervisión del estado de la caché : seguimiento de las tasas de aciertos, los errores y el rendimiento

Control de errores de caché L2 correctamente : la memoria caché L1 garantiza la resistencia.

Probar el comportamiento de la caché - verificación de escenarios de reinicio y conmutación por error

No usar la caché de memoria distribuida en producción : no persistente ni distribuida

No usar la memoria caché de sesión : tiene limitaciones significativas

No establecer la expiración más corta que la duración del token - fuerza una reautenticación innecesaria.

No olvide el uso compartido de claves de cifrado : los sistemas distribuidos necesitan claves compartidas