Table of Contents

Multi-replica and failover hosting

This page documents the Orleans.Lattice.Explorer packages, which are in progress, in the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at multi-replica-hosting.md, and llms.txt lists every page.

When the Explorer runs as a Blazor Server web app behind more than one replica, a signed-in operator can move between replicas during a session. That can happen on restart, scale-in, rolling deployment or load-balancer rebalance. Two pieces of auth state must survive that move:

  1. The browser session cookie must be decryptable on the replica that receives the request. ASP.NET Data Protection uses a key ring; the framework default is per-instance, so replica B cannot read a cookie issued by replica A. The Basic sign-in's credential cookie is encrypted with the same key ring, so a replica that cannot decrypt it treats the operator as signed out.
  2. A downstream State API token must be obtainable on the new replica. With the hosted-web Entra provider, Microsoft.Identity.Web reads a token cache. A cold replica has no token unless that cache is shared or the user is forced through a fresh interactive sign-in.

Both fixes are opt-in. Single-instance hosting keeps the default framework behaviour.

Durable auth state: a shared Data Protection key ring

Point every replica at one shared key ring so each can decrypt cookies issued by any other replica. The web head exposes this through LatticeExplorerWebOptions.

using Azure.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Orleans.Lattice.Explorer.Web;

TokenCredential keyRingCredential = ResolveCredential();

var builder = WebApplication.CreateBuilder();

builder.Services.AddLatticeExplorerWeb(options =>
{
    options.DataProtectionKeyRingBlobUri =
        new Uri("https://estate.blob.core.windows.net/keys/explorer-keyring.xml");
    options.DataProtectionKeyRingCredential = keyRingCredential;
    options.DataProtectionApplicationName = "lattice-explorer";
});

static TokenCredential ResolveCredential() => null!;

Setting DataProtectionKeyRingBlobUri without DataProtectionKeyRingCredential throws during registration. The head fails closed rather than silently using an ephemeral key ring.

For advanced Data Protection configuration, use ConfigureDataProtection. It runs after the built-in blob and application-name configuration.

using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;
using Orleans.Lattice.Explorer.Web;

var services = new ServiceCollection();

services.AddLatticeExplorerWeb(options =>
{
    options.ConfigureDataProtection = dataProtection =>
        dataProtection.SetDefaultKeyLifetime(TimeSpan.FromDays(30));
});

Durable auth state: a shared token cache

The hosted-web Entra provider uses Microsoft.Identity.Web to acquire downstream State API tokens. Select the distributed token cache and register one shared IDistributedCache for every replica. In a geo-distributed estate, point every region at the same estate-global cache so a failover replica can find the token acquired in another region.

See the Entra web configuration for the TokenCache option and cache guidance.

Graceful re-authentication

Even with a shared key ring and token cache, a token can expire or be revoked. When the credential provider reports that silent renewal is no longer possible, the session chrome shows a Your session expired interstitial. Its Sign in again button performs a full-page navigation to the configured re-authentication challenge.

The hosted-web Entra provider publishes a default challenge path and the host maps it with MapLatticeExplorerEntraWebReauth(). That endpoint issues an OpenID Connect challenge with prompt=login, redeeming a new authorization code even when the browser still holds a valid cookie.

A custom provider can point the interstitial at its own endpoint by registering ExplorerReauthOptions:

using Microsoft.Extensions.DependencyInjection;
using Orleans.Lattice.Explorer.Core.Authentication;

var services = new ServiceCollection();

services.AddSingleton(new ExplorerReauthOptions
{
    ChallengePath = "/my-auth/reauth",
    AppendReturnUrl = true,
    ReturnUrlParameter = "returnUrl",
});

When ChallengePath is unset, the interstitial falls back to a plain full-page reload. That is useful only for methods that can recover on reload.

Checklist

  • Persist the Data Protection key ring to shared storage with DataProtectionKeyRingBlobUri and DataProtectionKeyRingCredential.
  • Set one stable DataProtectionApplicationName across all replicas that share cookies.
  • For hosted-web Entra, use ExplorerWebTokenCacheKind.Distributed and register a shared IDistributedCache.
  • Map the forced-interactive re-authentication endpoint. With the hosted-web Entra provider this is app.MapLatticeExplorerEntraWebReauth().

See also