Conversation
|
Approve Database Migration
|
2 tasks
tjementum
added a commit
that referenced
this pull request
Jan 11, 2026
…en refresh (#826) ### Summary & Motivation Fix the token refresh race condition introduced in #821. When multiple API requests are made concurrently and all attempt to refresh an expired access token, only one succeeds while the others fail with `DbUpdateConcurrencyException`, causing the user to be logged out. This commonly occurs when returning to a browser tab after tokens have expired (e.g., TanStack Query's `invalidateQueries()` triggers multiple parallel requests). - Implement atomic token refresh using an isolated database connection that commits immediately, independent of EF Core's Unit of Work transaction. This ensures only one concurrent request wins the refresh race, while others fall back to the grace period mechanism using `PreviousRefreshTokenJti` - Update SQLite test connection strings to use shared cache mode (`Cache=Shared`), enabling isolated connections to access the same in-memory database. This allows the atomic refresh pattern using `Activator.CreateInstance(existingConnection.GetType())` to work in tests - Simplify `RefreshTokenGenerator` API by consolidating `Generate` and `Update` methods into a single `Generate` method with explicit version and expiry parameters - Reorder `UserInfoFactory` parameters to follow async conventions with `cancellationToken` last - Remove `SessionRefreshed` and `AuthenticationTokensRefreshed` telemetry events as they add noise without providing meaningful business value ### Downstream projects Update the SQLite connection string in `your-self-contained-system/Tests/EndpointBaseTest.cs` to use shared cache mode, which allows isolated database connections to access the same in-memory database: ```diff - // Create connection and add DbContext to the service collection - Connection = new SqliteConnection("DataSource=:memory:"); + // Create connection using shared cache mode so isolated connections can access the same in-memory database + Connection = new SqliteConnection($"Data Source=TestDb_{Guid.NewGuid():N};Mode=Memory;Cache=Shared"); ``` ### Checklist - [x] I have added tests, or done manual regression tests - [x] I have updated the documentation, if necessary
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary & Motivation
Implement secure refresh token rotation with replay attack detection by introducing a new Session aggregate that tracks refresh token chains in the database. This addresses a security gap where refresh tokens were previously stateless JWTs without server-side validation, making it impossible to detect token reuse or revoke sessions.
AddXForwarded()transform, ensuring client IP addresses are correctly captured in sessionsGrace period explained: When a refresh token is rotated, the previous token version remains valid for a 30-second grace period. This handles parallel requests where multiple API calls may use the same refresh token simultaneously. If request A triggers a token refresh while request B is still in flight with the old token, request B can still complete successfully within the grace period. Outside this window, using an old token is treated as a replay attack and the entire session is revoked, forcing re-authentication.
Downstream projects
Please note that this will invalidate all existing refresh tokens, and all users will be logged out after deploying this.
If the default HTTP request timeout (
DEFAULT_TIMEOUTinshared-webapp/infrastructure/http/httpClient.ts) has been extended beyond 30 seconds, also increaseGracePeriodSecondsinaccount-management/Core/Features/Authentication/Domain/Session.csto match. These values must stay in sync to prevent legitimate parallel requests from being incorrectly flagged as replay attacks.Checklist