ASP.NET Data Protection keys are stored in an ephemeral, container-local directory and are not encrypted at rest. On any container restart/redeploy the keyring is lost, and across swarm replicas the keys are not shared — both of which invalidate anything protected by those keys (auth cookies, antiforgery tokens, OIDC correlation/nonce, any IDataProtector payloads).
Evidence (Seq, Warning)
[Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository]
Storing keys in a directory '/home/app/.aspnet/DataProtection-Keys' that may not be
persisted outside of the container. Protected data will be unavailable when container
is destroyed. (https://aka.ms/aspnet/dataprotectionwarning)
[Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager]
No XML encryptor configured. Key {..} may be persisted to storage in unencrypted form.
Impact
Every redeploy logs users out and breaks in-flight flows that rely on protected payloads (antiforgery failures, OIDC login "correlation failed").
Multi-replica risk: replicas generate/hold different keyrings, so a cookie encrypted by replica A fails to decrypt on replica B -> intermittent auth failures under scale-out (not just on restart).
At rest: keys are stored unencrypted.
Proposed fix
Configure a shared, persistent, encrypted key ring in the API/services DI, e.g.:
services.AddDataProtection().SetApplicationName("spikersoft")// stable, shared across replicas/services// persist to a shared store — Redis cluster already exists here:.PersistKeysToStackExchangeRedis(redisConn,"DataProtection-Keys")// or a shared volume mounted on all nodes:// .PersistKeysToFileSystem(new DirectoryInfo("/var/spikersoft/dp-keys")).ProtectKeysWith...(...);// encrypt at rest (cert/KMS)
Given a Redis cluster is already deployed, PersistKeysToStackExchangeRedis is the lowest-friction option and fixes both the persistence and the multi-replica sharing problem.
Verify
After deploy, the FileSystemXmlRepository / No XML encryptor warnings disappear from Seq.
A redeploy no longer forces re-login; keys visible in the shared store.
Filed proactively by automated health check (Seq Warning audit).
## Summary
ASP.NET **Data Protection keys are stored in an ephemeral, container-local directory and are not encrypted at rest**. On any container restart/redeploy the keyring is lost, and across swarm replicas the keys are not shared — both of which invalidate anything protected by those keys (auth cookies, antiforgery tokens, OIDC correlation/nonce, any `IDataProtector` payloads).
## Evidence (Seq, Warning)
```
[Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository]
Storing keys in a directory '/home/app/.aspnet/DataProtection-Keys' that may not be
persisted outside of the container. Protected data will be unavailable when container
is destroyed. (https://aka.ms/aspnet/dataprotectionwarning)
[Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager]
No XML encryptor configured. Key {..} may be persisted to storage in unencrypted form.
```
## Impact
- **Every redeploy logs users out** and breaks in-flight flows that rely on protected payloads (antiforgery failures, OIDC login "correlation failed").
- **Multi-replica risk:** replicas generate/hold different keyrings, so a cookie encrypted by replica A fails to decrypt on replica B -> intermittent auth failures under scale-out (not just on restart).
- **At rest:** keys are stored unencrypted.
## Proposed fix
Configure a shared, persistent, encrypted key ring in the API/services DI, e.g.:
```csharp
services.AddDataProtection()
.SetApplicationName("spikersoft") // stable, shared across replicas/services
// persist to a shared store — Redis cluster already exists here:
.PersistKeysToStackExchangeRedis(redisConn, "DataProtection-Keys")
// or a shared volume mounted on all nodes:
// .PersistKeysToFileSystem(new DirectoryInfo("/var/spikersoft/dp-keys"))
.ProtectKeysWith...(...); // encrypt at rest (cert/KMS)
```
Given a Redis cluster is already deployed, `PersistKeysToStackExchangeRedis` is the lowest-friction option and fixes both the persistence and the multi-replica sharing problem.
## Verify
- After deploy, the `FileSystemXmlRepository` / `No XML encryptor` warnings disappear from Seq.
- A redeploy no longer forces re-login; keys visible in the shared store.
Priority: medium-high (auth stability + security).
---
_Filed proactively by automated health check (Seq Warning audit)._
Triage — implementable once you pick the key store; parking the decision here rather than guessing on auth-critical infrastructure. Two viable shapes given our stack: (a) PersistKeysToStackExchangeRedis against the existing redis cluster (fixes BOTH redeploy-logout and replica-sharing; keys survive as long as redis does — note redis is cache-class here and only recently gained persistence, #371) or (b) PersistKeysToFileSystem on a SERVER bind mount (simplest, survives anything, but single-node — fine while the API is pinned to SERVER, breaks if the API ever joins the spikersoft-app burst pool). Both need SetApplicationName("spikersoft"). At-rest encryption (the second warning) needs ProtectKeysWithCertificate — requires a cert/secret you'd have to provision; can land as a follow-up to the persistence fix. Say the word on (a) or (b) and it's a small PR.
Triage — implementable once you pick the key store; parking the decision here rather than guessing on auth-critical infrastructure. Two viable shapes given our stack: (a) PersistKeysToStackExchangeRedis against the existing redis cluster (fixes BOTH redeploy-logout and replica-sharing; keys survive as long as redis does — note redis is cache-class here and only recently gained persistence, #371) or (b) PersistKeysToFileSystem on a SERVER bind mount (simplest, survives anything, but single-node — fine while the API is pinned to SERVER, breaks if the API ever joins the spikersoft-app burst pool). Both need SetApplicationName("spikersoft"). At-rest encryption (the second warning) needs ProtectKeysWithCertificate — requires a cert/secret you'd have to provision; can land as a follow-up to the persistence fix. Say the word on (a) or (b) and it's a small PR.
Resolved in spikersoft-backend PR #218 (merged to master, commit c019f3e). New AddDataProtectionConfiguration persists the Data Protection key ring to the existing Redis cluster (RedisXmlRepository, shared across replicas + survives redeploy) with SetApplicationName("spikersoft"), and encrypts keys at rest via ProtectKeysWithCertificate when DataProtection:CertificatePath is provisioned (absent = non-fatal, warning logged, so deploys never brick before the cert exists). 5 xUnit wiring tests. Store=Redis and encryptor-in-PR per decision.
Ops follow-up to fully close the at-rest half: provision the PKCS#12 cert secret and set DataProtection__CertificatePath (+ __CertificatePassword) on the API stack — until then keys persist to Redis unencrypted with a startup warning. Closing the ticket; the cert provisioning is a small config step, not code.
Closing.
Resolved in spikersoft-backend PR #218 (merged to `master`, commit c019f3e). New `AddDataProtectionConfiguration` persists the Data Protection key ring to the existing **Redis cluster** (`RedisXmlRepository`, shared across replicas + survives redeploy) with `SetApplicationName("spikersoft")`, and encrypts keys at rest via `ProtectKeysWithCertificate` when `DataProtection:CertificatePath` is provisioned (absent = non-fatal, warning logged, so deploys never brick before the cert exists). 5 xUnit wiring tests. Store=Redis and encryptor-in-PR per decision.
**Ops follow-up to fully close the at-rest half:** provision the PKCS#12 cert secret and set `DataProtection__CertificatePath` (+ `__CertificatePassword`) on the API stack — until then keys persist to Redis unencrypted with a startup warning. Closing the ticket; the cert provisioning is a small config step, not code.
Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
ASP.NET Data Protection keys are stored in an ephemeral, container-local directory and are not encrypted at rest. On any container restart/redeploy the keyring is lost, and across swarm replicas the keys are not shared — both of which invalidate anything protected by those keys (auth cookies, antiforgery tokens, OIDC correlation/nonce, any
IDataProtectorpayloads).Evidence (Seq, Warning)
Impact
Proposed fix
Configure a shared, persistent, encrypted key ring in the API/services DI, e.g.:
Given a Redis cluster is already deployed,
PersistKeysToStackExchangeRedisis the lowest-friction option and fixes both the persistence and the multi-replica sharing problem.Verify
FileSystemXmlRepository/No XML encryptorwarnings disappear from Seq.Priority: medium-high (auth stability + security).
Filed proactively by automated health check (Seq Warning audit).
Triage — implementable once you pick the key store; parking the decision here rather than guessing on auth-critical infrastructure. Two viable shapes given our stack: (a) PersistKeysToStackExchangeRedis against the existing redis cluster (fixes BOTH redeploy-logout and replica-sharing; keys survive as long as redis does — note redis is cache-class here and only recently gained persistence, #371) or (b) PersistKeysToFileSystem on a SERVER bind mount (simplest, survives anything, but single-node — fine while the API is pinned to SERVER, breaks if the API ever joins the spikersoft-app burst pool). Both need SetApplicationName("spikersoft"). At-rest encryption (the second warning) needs ProtectKeysWithCertificate — requires a cert/secret you'd have to provision; can land as a follow-up to the persistence fix. Say the word on (a) or (b) and it's a small PR.
Resolved in spikersoft-backend PR #218 (merged to
master, commit c019f3e). NewAddDataProtectionConfigurationpersists the Data Protection key ring to the existing Redis cluster (RedisXmlRepository, shared across replicas + survives redeploy) withSetApplicationName("spikersoft"), and encrypts keys at rest viaProtectKeysWithCertificatewhenDataProtection:CertificatePathis provisioned (absent = non-fatal, warning logged, so deploys never brick before the cert exists). 5 xUnit wiring tests. Store=Redis and encryptor-in-PR per decision.Ops follow-up to fully close the at-rest half: provision the PKCS#12 cert secret and set
DataProtection__CertificatePath(+__CertificatePassword) on the API stack — until then keys persist to Redis unencrypted with a startup warning. Closing the ticket; the cert provisioning is a small config step, not code.Closing.