You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Run a self-hosted Bitwarden lite instance (v2026.8.1) with a premium user and a valid license at /etc/bitwarden/licenses/user/{selfHostedUserId}.json.
Log in on a mobile client (reproduced with Bitwarden iOS 2026.8.0) so the client holds a refresh token.
Trigger two overlapping POST /identity/connect/token requests for that user (refresh-token grant). This happens naturally when the iOS app retries / opens in the background; it can also be done with two parallel curls using the same refresh token.
Observe Identity logs and the client receiving HTTP 504 / an unexpected error.
Expected Result
Token issuance succeeds (or returns a normal OAuth error). Concurrent refresh requests for the same premium user must not crash Identity.
Actual Result
The first overlapping request can succeed or fail; the second throws an unhandled exception while building the access token. Identity then serves 504s until the process restarts.
System.ArgumentException: An item with the same key has already been added. Key: {selfHostedUserId}
at System.Collections.Generic.Dictionary`2.Add(...)
at Bit.Core.Billing.Services.LicensingService.ValidateUserPremiumAsync(User user)
in /source/src/Core/Billing/Services/Implementations/LicensingService.cs:line 249
at Bit.Identity.IdentityServer.ProfileService.GetProfileDataAsync(...)
at Duende.IdentityServer.ResponseHandling.TokenResponseGenerator.ProcessRefreshTokenRequestAsync(...)
Follow-up requests then hit:
System.InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state.
at Bit.Core.Billing.Services.LicensingService.ValidateUserPremiumAsync(User user)
This is not a duplicate license file. There is a single user/{selfHostedUserId}.json. The dictionary key is the self-hosted user id; the Id inside the JSON is the cloud user id (expected).
Two concurrent token requests for the same premium user both miss the cache and both call .Add. The unhandled exception aborts /identity/connect/token.
Suggested fix: use ConcurrentDictionary<Guid, DateTime> (or IMemoryCache) and TryAdd / indexer assignment instead of Dictionary.Add.
Related: #4274 (same stack traces; closed as an email/license mismatch; the race was not fixed).
Build Version
Bitwarden lite 2026.8.1 (ghcr.io/bitwarden/lite). Reproduced against server tag v2026.8.1; the racy _userCheckCache.Add is still on master.
Environment
Self-Hosted
Environment Details
Deployment: Kubernetes, Bitwarden lite (single container)
I understand that work is tracked outside of Github. A PR will be linked to this issue should one be opened to address it, but Bitwarden doesn't use fields like "assigned", "milestone", or "project" to track progress.
Steps To Reproduce
/etc/bitwarden/licenses/user/{selfHostedUserId}.json.POST /identity/connect/tokenrequests for that user (refresh-token grant). This happens naturally when the iOS app retries / opens in the background; it can also be done with two parallel curls using the same refresh token.Expected Result
Token issuance succeeds (or returns a normal OAuth error). Concurrent refresh requests for the same premium user must not crash Identity.
Actual Result
The first overlapping request can succeed or fail; the second throws an unhandled exception while building the access token. Identity then serves 504s until the process restarts.
Follow-up requests then hit:
This is not a duplicate license file. There is a single
user/{selfHostedUserId}.json. The dictionary key is the self-hosted user id; theIdinside the JSON is the cloud user id (expected).Screenshots or Videos
No response
Additional Context
LicensingServicekeeps an in-memory cache:In
ValidateUserPremiumAsync(still present onmaster):Two concurrent token requests for the same premium user both miss the cache and both call
.Add. The unhandled exception aborts/identity/connect/token.Suggested fix: use
ConcurrentDictionary<Guid, DateTime>(orIMemoryCache) andTryAdd/ indexer assignment instead ofDictionary.Add.Related: #4274 (same stack traces; closed as an email/license mismatch; the race was not fixed).
Build Version
Bitwarden lite
2026.8.1(ghcr.io/bitwarden/lite). Reproduced against server tagv2026.8.1; the racy_userCheckCache.Addis still onmaster.Environment
Self-Hosted
Environment Details
http://+:5005Issue Tracking Info