Search code examples
c#.netazureredisazure-redis-cache

Azure Redis Cache with Entra ID Authentication


I am trying to add Azure Redis Cache to my .NET 8 api. I added the connectionString and added the code as:

services.AddStackExchangeRedisCache(
    options =>
    {
        options.Configuration = builder.Configuration.GetConnectionString("RedisCache");
        options.InstanceName = builder.Environment.EnvironmentName;
    })
.AddDistributedMemoryCache();

This is working when I added my password in the connectionString. But I do not want to do this, I want to use Entra ID authentication. So I have enabled the option, and also selected the web app (also in azure) which needs to use it. Access Policy as data owner. But when I leave out the password, I am getting an error:

StackExchange.Redis.RedisConnectionException: 'The message timed out in the backlog attempting to send because no connection became available (5000ms) - Last Connection Exception: AuthenticationFailure on tsg-pas-dev-redis.redis.cache.windows.net:6380/Interactive, Flushed/ComputeResult, last: ECHO, origin: SetResult, outstanding: 0, last-read: 0s ago, last-write: 0s ago, keep-alive: 60s, state: ConnectedEstablishing, mgr: 9 of 10 available, last-heartbeat: never, global: 0s ago, v: 2.6.122.38350, command=SET, timeout: 5000, inst: 12, qu: 1, qs: 0, aw: False, bw: SpinningDown, rs: ReadAsync, ws: Flushed, in: 0, last-in: 0, cur-in: 0, sync-ops: 1, async-ops: 1, serverEndpoint: tsg-pas-dev-redis.redis.cache.windows.net:6380, conn-sec: 0.23, aoc: 0, mc: 1/1/0, mgr: 10 of 10 available, clientName: DBSX1Y6-54977(SE.Redis-v2.6.122.38350), IOCP: (Busy=1,Free=999,Min=8,Max=1000), WORKER: (Busy=1,Free=32766,Min=8,Max=32767), POOL: (Threads=15,QueuedItems=0,CompletedItems=347,Timers=3), v: 2.6.122.38350 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts)'

Exception: Error: NOAUTH Authentication required. Verify if the Redis password provided is correct. Attempted command: ECHO

Any clue what I am missing? I remember with one of the other services, I had to do something in the main, to fetch/initialize the token... but no clue if it is related or not.

-- EDIT: After the answer below I made a small change so I can still use IDistributedCache, because locally i can still use MemoryCache:

public static void AddDistributedCache(this IServiceCollection services, WebApplicationBuilder builder)
{
    if (builder.Configuration["UseMemoryCache"]?.Equals("yes", StringComparison.OrdinalIgnoreCase) ?? true)
    {
        services.AddDistributedMemoryCache();
    }
    else
    {
        services.AddStackExchangeRedisCache(options =>
        {
            options.InstanceName = builder.Environment.EnvironmentName;
            options.ConnectionMultiplexerFactory = () => ConnectionMultiplexerFactory(builder);
        })
        .AddDistributedMemoryCache();
    }
}

private static async Task<IConnectionMultiplexer> ConnectionMultiplexerFactory(WebApplicationBuilder builder)
{
    var configurationOptions = await ConfigurationOptions.Parse(builder.Configuration.GetConnectionString("RedisCache")!)
        .ConfigureForAzureWithTokenCredentialAsync("XXXX", new DefaultAzureCredential());

    var connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(configurationOptions);

    return connectionMultiplexer;
}

The XXX needs to come from appsettings, allthough I do not understand yet why I need it. And not very handy when using multiple apps on the same redis ache instance yet I guess... But at least no passwords :) The good thing about this is that I can easily switch and also do not have to worry about multiplexer getDatabase etc...


Solution

  • Based on the documentation available here, in order to use Entra ID authentication, you would need to use Microsoft.Azure.StackExchangeRedis Nuget package.

    Code sample for the same is available here: https://github.com/Azure/Microsoft.Azure.StackExchangeRedis/tree/main/sample.

    From this link, sample code to use DefaultAzureCredential:

    Write("Redis cache host name: ");
    cacheHostName = ReadLine()?.Trim();
    Write("'Username' from the 'Data Access Configuration' blade on the Azure Cache for Redis resource): ");
    var username = ReadLine()?.Trim();
    
    Write("Connecting using TokenCredential...");
    configurationOptions = await ConfigurationOptions.Parse($"{cacheHostName}:6380").ConfigureForAzureWithTokenCredentialAsync(username!, new DefaultAzureCredential());
    configurationOptions.AbortOnConnectFail = true; // Fail fast for the purposes of this sample. In production code, this should remain false to retry connections on startup
    LogTokenEvents(configurationOptions);
    
    connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(configurationOptions, connectionLog);