Skip to content

feat: Implement refresh token functionality and enhance authentication#51

Merged
taiphanvan2k3 merged 2 commits intomainfrom
feat/handle-remember-me-and-refresh-token
Nov 21, 2025
Merged

feat: Implement refresh token functionality and enhance authentication#51
taiphanvan2k3 merged 2 commits intomainfrom
feat/handle-remember-me-and-refresh-token

Conversation

@taiphanvan2k3
Copy link
Copy Markdown
Member

@taiphanvan2k3 taiphanvan2k3 commented Nov 21, 2025

Summary by CodeRabbit

  • New Features

    • Added a refresh token endpoint to obtain new access tokens without re-authenticating.
    • Implemented remember-me functionality for extended session expiration.
    • Added token rotation and security violation detection with automatic token chain revocation.
  • Refactor

    • Separated access and refresh token expiration times for improved security control.
    • Updated registration response to exclude tokens; now includes user roles and avatar URL instead.

✏️ Tip: You can customize this high-level summary in your review settings.

…n responses

- Added RefreshAccessTokenCommand and RefreshAccessTokenCommandHandler to handle token refresh logic.
- Introduced RefreshToken entity and repository for managing refresh tokens in the database.
- Updated LoginCommand and LoginExternalCommand to include RememberMe flag for token persistence.
- Enhanced LoginResponse and LoginExternalResponse to include access and refresh token expiration times.
- Updated AuthController to include a new endpoint for refreshing access tokens.
- Refactored ITokenService to return access token with expiration time.
- Implemented validation for refresh token commands.
… revocation logic

- Introduced SecurityException to handle security violations during token refresh.
- Updated RefreshAccessTokenCommandHandler to revoke entire token chains upon detection of compromised tokens.
- Enhanced IRefreshTokenRepository methods to include reason for token revocation.
- Added RevokedReason property to RefreshToken entity for tracking revocation reasons.
- Implemented migration for the new RefreshTokens table structure including RevokedReason.
- Updated GlobalExceptionMiddleware to handle SecurityException and return appropriate responses.
@taiphanvan2k3 taiphanvan2k3 self-assigned this Nov 21, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 21, 2025

Walkthrough

Implements a comprehensive refresh token feature with token rotation, revocation, and security violation handling. Introduces RefreshToken domain entity, repository, and command handler; updates token service to return expiration dates; modifies login handlers to persist refresh tokens; removes token generation from registration; and adds endpoint for refreshing access tokens with chain revocation on compromise.

Changes

Cohort / File(s) Summary
SecurityException Addition
src/Application/Common/Exceptions/SecurityException.cs
New sealed exception type for security violations with constructors accepting message and optional inner exception
Login Feature Token Updates
src/Application/Features/Auth/Login/LoginCommand.cs, src/Application/Features/Auth/Login/LoginCommandHandler.cs
LoginResponse replaces ExpiresAt with AccessTokenExpiresAt and RefreshTokenExpiresAt; handler now accepts IRefreshTokenRepository, generates and persists refresh tokens based on RememberMe flag
External Login Feature Updates
src/Application/Features/Auth/LoginExternal/LoginExternalCommand.cs, src/Application/Features/Auth/LoginExternal/LoginExternalCommandHandler.cs, src/Application/Features/Auth/LoginExternal/LoginExternalResponse.cs
LoginExternalCommand adds RememberMe property; response and handler updated to use AccessTokenExpiresAt; handler persists refresh tokens with expiration tied to RememberMe
Registration Feature Simplification
src/Application/Features/Auth/Register/RegisterCommandHandler.cs, src/Application/Features/Auth/Register/RegisterResponse.cs
Removed token generation from registration flow; RegisterCommandHandler no longer depends on ITokenService; RegisterResponse now includes Roles and AvatarUrl, removes tokens, renames Id to UserId
Refresh Token Command Feature
src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommand.cs, src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommandHandler.cs, src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommandValidator.cs
New CQRS feature implementing refresh token validation, token rotation, and chain revocation on security violations; handler revokes compromised token chain and entire user token set if needed
Token Service Refactoring
src/Application/Interfaces/Services/Auth/ITokenService.cs, src/Infrastructure/Services/TokenService.cs
GenerateAccessToken now returns tuple with expiration; ValidateAccessTokenAsync removed; implementation refactored to separate public and private token generation methods
Token Constants and Domain Entity
src/Domain/Constants/TokenConstants.cs, src/Domain/Entities/RefreshToken.cs
New constants for refresh token expiration (1 day normal, 30 days with RememberMe) and revocation reasons; RefreshToken entity supports rotation via ReplacedByToken and lifecycle management with revocation tracking
Refresh Token Repository
src/Application/Interfaces/Repositories/IRefreshTokenRepository.cs, src/Infrastructure/Repositories/RefreshTokenRepository.cs
New interface and EF Core implementation for CRUD operations, token validation, revocation (individual and bulk), active token retrieval, and cleanup of expired tokens
Database Infrastructure
src/Infrastructure/Data/Configurations/RefreshTokenConfiguration.cs, src/Infrastructure/Data/Contexts/DataContext.cs, src/Infrastructure/Data/Migrations/20251121160401_CreateRefreshTokenTable.cs, src/Infrastructure/Data/Migrations/20251121160401_CreateRefreshTokenTable.Designer.cs, src/Infrastructure/Data/Migrations/DataContextModelSnapshot.cs
EF Core configuration for RefreshTokens table with indexes on Token (unique), ExpiresAt, UserId, and composite (UserId, RevokedAt); DbContext updated with RefreshTokens DbSet; migration adds table with cascade delete from Users
Dependency Injection and API Integration
src/Infrastructure/Extensions/ServiceCollectionExtensions.cs, src/Web.Api/Controllers/V1/AuthController.cs, src/Web.Api/Middleware/GlobalExceptionMiddleware.cs
IRefreshTokenRepository registered in DI container; new POST /api/v{version}/auth/refresh-access-token endpoint added; GlobalExceptionMiddleware handles SecurityException with 401 Unauthorized response

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant Handler as RefreshAccessTokenCommandHandler
    participant TokenSvc as ITokenService
    participant TokenRepo as IRefreshTokenRepository
    participant UnitOfWork
    
    Client->>Controller: POST /refresh-access-token (RefreshToken)
    Controller->>Handler: Handle(command, cancellationToken)
    
    Handler->>TokenRepo: GetByTokenAsync(token)
    TokenRepo-->>Handler: refreshToken (or null)
    
    alt Token not found or invalid
        Handler-->>Controller: UnauthorizedException
    else Token expired
        Handler-->>Controller: UnauthorizedException
    else Token revoked (compromised)
        Handler->>Handler: RevokeEntireTokenChainAsync(token)
        Handler->>TokenRepo: RevokeAsync (all chain tokens)
        Handler->>TokenRepo: RevokeAllForUserAsync (all user tokens)
        Handler->>UnitOfWork: SaveChangesAsync
        Handler-->>Controller: SecurityException
    else Token valid
        Handler->>TokenSvc: GenerateAccessToken(user)
        TokenSvc-->>Handler: (accessToken, expiresAt)
        Handler->>TokenSvc: GenerateRefreshToken(user)
        TokenSvc-->>Handler: newRefreshToken
        
        Handler->>TokenRepo: RevokeAsync(oldToken, "TokenRotation")
        Handler->>TokenRepo: AddAsync(newRefreshToken)
        Handler->>UnitOfWork: SaveChangesAsync
        
        Handler-->>Controller: Result.Success(RefreshAccessTokenResponse)
        Controller-->>Client: 200 OK with new tokens
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Areas requiring careful attention:

  • Token revocation chain logic (RefreshAccessTokenCommandHandler.RevokeEntireTokenChainAsync): Verify the recursive/chain revocation correctly handles the ReplaceBy relationships and doesn't miss any token paths
  • Token expiration calculation: Confirm AccessTokenExpiresAt tuple return and RememberMe-based expiration (1 day vs. 30 days) are correctly propagated across all handlers (LoginCommandHandler, LoginExternalCommandHandler, TokenService)
  • Database migration and constraints: Validate foreign key cascade delete, unique constraint on Token column, and composite index on (UserId, RevokedAt) are correct for query performance
  • Handler dependency injection changes: Ensure all refactored handler constructors (LoginCommandHandler, LoginExternalCommandHandler, RegisterCommandHandler) have updated call sites and tests
  • SecurityException mapping: Verify GlobalExceptionMiddleware correctly catches and responds with 401 for SecurityException
  • Registration flow removal: Confirm removal of token generation in RegisterCommandHandler doesn't break downstream processes expecting tokens in registration response

Possibly related PRs

  • PR #41: Both modify LoginExternalCommand/Response and LoginExternalCommandHandler alongside token generation interfaces, indicating direct overlap in external login token handling
  • PR #48: Both update GlobalExceptionMiddleware exception-to-response mapping; this PR adds SecurityException handling while the other modifies default error content
  • PR #47: Both modify GlobalExceptionMiddleware's exception inspection and HTTP response mapping logic

Poem

🐰 A rabbit hops through tokens spun,
Rotating secrets, one by one!
When chains grow long, we trim with care,
Fresh tokens bloom in autumn air.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.27% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: implementing refresh token functionality and enhancing authentication, which aligns with the substantial refactoring across login flows, token generation, and new refresh token infrastructure.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/handle-remember-me-and-refresh-token

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (17)
src/Application/Features/Auth/LoginExternal/LoginExternalResponse.cs (1)

20-24: Consider adding RefreshTokenExpiresAt for external login consistency

LoginExternalResponse exposes RefreshToken but only AccessTokenExpiresAt. LoginResponse now includes both AccessTokenExpiresAt and RefreshTokenExpiresAt; mirroring that here would simplify client handling and keep the auth surface consistent across login flows.

src/Application/Interfaces/Services/Auth/ITokenService.cs (1)

10-23: New GenerateAccessToken signature looks good; consider a small result DTO if this grows

Returning (string accessToken, DateTimeOffset accessTokenExpiresAt) matches the updated TokenService implementation and makes expiration explicit. If you later add more metadata (e.g., scopes, token type), a small AccessTokenResult DTO could improve readability and versioning, but the tuple is perfectly fine for the current needs. Based on relevant code snippets.

src/Domain/Constants/TokenConstants.cs (1)

1-54: Good centralization of token expirations and revocation reasons

The TokenConstants class nicely removes magic TimeSpans and reason strings from the domain logic. If these reasons are ever compared programmatically or localized, consider using short reason codes or an enum plus a separate mapping to display strings; otherwise this shape is fine.

src/Application/Features/Auth/Register/RegisterResponse.cs (1)

33-41: Roles and avatar fields fit new registration flow; consider read‑only collection

The shift from tokens to Roles and AvatarUrl matches the tokenless registration behavior and looks correct.

If you don’t need to mutate Roles after creation, consider exposing IReadOnlyList<string> or string[] instead of List<string> to avoid accidental modification of the response object post‑construction. This is optional but can make the DTO contract clearer.

src/Application/Features/Auth/LoginExternal/LoginExternalCommand.cs (1)

43-46: Defaulting RememberMe to true changes external login persistence behavior

With public bool RememberMe { get; init; } = true;, any client that omits this field will now get “remember me” semantics (longer‑lived refresh tokens) by default. That’s a meaningful behavioral and security change.

If the intent is to make persistence an explicit opt‑in (and to align with the regular login flow, if that defaults to non‑persistent), consider defaulting to false:

-    public bool RememberMe { get; init; } = true;
+    public bool RememberMe { get; init; } = false;

Please confirm that the current default aligns with your product/security requirements.

src/Application/Common/Exceptions/SecurityException.cs (1)

1-16: Custom SecurityException implementation is straightforward and appropriate

The sealed SecurityException type with standard message/inner‑exception constructors is correct and integrates cleanly with the middleware mapping.

Note: its name overlaps with System.Security.SecurityException, so if you later need both types in the same file you may want to use fully qualified names or a using alias, but that’s not a blocker here.

src/Web.Api/Controllers/V1/AuthController.cs (2)

20-47: Align XML docs with current token/registration behavior

The updated login XML comments describing access token (15 minutes) and refresh token (1/30 days) are helpful, but they now couple documentation to configuration. If these values are driven by configuration or TokenConstants, make sure they stay in sync over time.

Also, the registration endpoint docs still say “Registration result with user info and tokens”, but RegisterResponse no longer includes tokens. It would be clearer to update the text, for example:

-    /// <returns>Registration result with user info and tokens</returns>
+    /// <returns>Registration result with user information</returns>

This keeps the public contract documentation aligned with the current behavior.


120-142: Refresh endpoint behavior and response envelope consistency

The new refresh-access-token endpoint is wired correctly to MediatR and returns a success ApiResponse<RefreshAccessTokenResponse> when result.IsSuccess is true. A couple of behavioral points to consider:

  1. Envelope consistency
    Other auth endpoints (login, login-external, register) return bare response types on 200 (e.g., LoginResponse), whereas this action returns an ApiResponse<RefreshAccessTokenResponse>. If clients consume these endpoints together, you may want a consistent envelope strategy (all wrapped or all bare) to avoid special‑casing this route.

  2. Error status codes
    All non‑successful MediatR results here are mapped to 400 Bad Request:

    return BadRequest(ApiResponse<object>.CreateFailure(result.Error!.Description));

    If the command handler differentiates error types (e.g., unauthorized vs validation vs conflict), you might want to mirror the pattern used in Register/VerifyEmail and map them to more specific HTTP status codes, or ensure that truly unauthorized cases throw SecurityException/UnauthorizedException so they go through the global middleware instead of this 400 path.

Neither of these is a correctness bug, but aligning them can simplify client integration and make error semantics clearer.

src/Application/Features/Auth/Register/RegisterCommandHandler.cs (1)

17-21: Registration handler now correctly focuses on account creation only

Removing token generation from registration and returning a RegisterResponse with UserId and basic profile/role data makes this handler single‑purpose and consistent with the new login/refresh flows. Just ensure API clients are updated to call the login endpoint after successful registration instead of expecting tokens here.

Also applies to: 76-84

src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommandHandler.cs (3)

18-28: Constructor and dependency usage look good, but unit-of-work use is asymmetric

The injected dependencies are validated correctly and stored as readonly fields. However, IUnitOfWork is only used in the revoked‑token branch, not on the successful refresh path. If your architectural pattern expects handlers to commit via IUnitOfWork.SaveChangesAsync, consider either:

  • consistently committing after updating/adding refresh tokens in the success path, or
  • relying entirely on a pipeline/outer unit-of-work and removing the explicit SaveChangesAsync in the revoked branch.

Right now the revoked path is committed explicitly while the normal path is not, which makes persistence semantics harder to reason about.


69-89: Refresh rotation flow is correct; consider explicit persistence and collision handling strategy

The rotation logic (generate new access + refresh, revoke old token with TokenRotation, create new RefreshToken entity with the same persistence policy) is aligned with best practices. Two follow‑ups worth considering:

  • Persistence: similar to the earlier comment, confirm where SaveChanges is invoked for this success path so revocation and the new token insert are guaranteed to be committed.
  • Uniqueness: the DB enforces a unique index on Token. If GenerateRefreshToken ever collides, AddAsync will fail. If this is a concern for you, you might want a retry loop on GenerateRefreshToken when a unique‑constraint violation occurs.

These are more about robustness than correctness of the current logic.


100-145: RevokeEntireTokenChainAsync is safe; minor redundancy with RevokeAllForUserAsync

The chain traversal via ReplacedByToken plus the processedTokens guard handles potential cycles and correctly collects tokens to revoke. Calling RevokeAllForUserAsync afterwards provides a strong security guarantee.

This does mean that tokens in tokensToRevoke may be affected twice, depending on repository implementation. If RevokeAllForUserAsync only targets still‑active tokens, this is fine; otherwise, you might be doing redundant work or overriding the more specific TokenChainAssassination reason with SecurityBreachAllTokensRevoked. Not a blocker, but worth checking the repository behavior.

src/Application/Features/Auth/Login/LoginCommandHandler.cs (1)

60-72: Refresh token persistence flow looks correct; align transaction strategy with refresh handler

The sequence

  • generate access token + expiry,
  • generate refresh token value,
  • create RefreshToken entity with isPersistent = request.RememberMe,
  • AddAsync via IRefreshTokenRepository,

is logically sound and matches the rotation semantics used in the refresh handler.

Just make sure your transaction / unit‑of‑work story is consistent with RefreshAccessTokenCommandHandler so that both login and refresh paths persist refresh tokens in the same way (either via a pipeline or explicit SaveChanges).

src/Application/Interfaces/Repositories/IRefreshTokenRepository.cs (1)

8-44: Repository surface is appropriate; clarify “active” and cleanup semantics in implementation

The interface covers the needed operations for login/refresh flows and admin/cleanup tasks. When implementing:

  • Define “active” in GetActiveTokensByUserIdAsync consistently with RefreshToken.IsActive (not expired, not revoked, and possibly not soft‑deleted).
  • Ensure CleanExpiredTokensAsync clearly documents whether it physically deletes rows or just marks them as revoked/deleted, so callers know what to expect.

No changes needed here, just implementation‑level clarity.

src/Infrastructure/Repositories/RefreshTokenRepository.cs (2)

32-37: Active token filter is strict on expiry and may have redundant conditions

The active-token query:

.Where(rt => rt.UserId == userId
             && rt.RevokedAt == null
             && rt.ExpiresAt > DateTimeOffset.UtcNow
             && rt.RevokedReason == null)
  • RevokedReason == null is likely redundant if Revoke always sets both RevokedAt and RevokedReason.
  • Using > against DateTimeOffset.UtcNow means a token with ExpiresAt == now is already treated as inactive; confirm that this matches how you validate tokens elsewhere.

If these aren’t intentional invariants, you could simplify the predicate or align it with your validation logic.


64-76: CleanExpiredTokensAsync semantics and performance

Two points to consider:

  1. The filter removes all revoked tokens, even if they have not yet expired:
.Where(rt => rt.ExpiresAt < DateTimeOffset.UtcNow || rt.RevokedAt != null)

If you rely on revoked tokens for reuse detection or audit, you may want to either:

  • Restrict cleanup to ExpiresAt < now, or
  • Keep revoked-but-unexpired tokens for some retention window.
  1. For large token tables, materializing expiredTokens into memory and then calling RemoveRange can be inefficient. EF Core 8 supports ExecuteDeleteAsync, which can do a set‑based delete in the database without loading entities.

These aren’t correctness bugs but could be tightened for clearer intent and better scalability.

src/Infrastructure/Services/TokenService.cs (1)

27-37: Access‑token tuple API looks good; consider minor time/config polish

The new signature and expiry calculation align with the interface and refresh‑token flow. Two small improvements you might consider:

  • Use DateTimeOffset.UtcNow for the expiry you return, to avoid implicit DateTimeDateTimeOffset conversion.
  • Use int.TryParse (with a sane default) for ExpirationHours to better handle misconfigured values.

These are not blockers, just polish.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 677ef8b and 2190a9e.

📒 Files selected for processing (25)
  • src/Application/Common/Exceptions/SecurityException.cs (1 hunks)
  • src/Application/Features/Auth/Login/LoginCommand.cs (2 hunks)
  • src/Application/Features/Auth/Login/LoginCommandHandler.cs (4 hunks)
  • src/Application/Features/Auth/LoginExternal/LoginExternalCommand.cs (1 hunks)
  • src/Application/Features/Auth/LoginExternal/LoginExternalCommandHandler.cs (6 hunks)
  • src/Application/Features/Auth/LoginExternal/LoginExternalResponse.cs (1 hunks)
  • src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommand.cs (1 hunks)
  • src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommandHandler.cs (1 hunks)
  • src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommandValidator.cs (1 hunks)
  • src/Application/Features/Auth/Register/RegisterCommandHandler.cs (2 hunks)
  • src/Application/Features/Auth/Register/RegisterResponse.cs (1 hunks)
  • src/Application/Interfaces/Repositories/IRefreshTokenRepository.cs (1 hunks)
  • src/Application/Interfaces/Services/Auth/ITokenService.cs (1 hunks)
  • src/Domain/Constants/TokenConstants.cs (1 hunks)
  • src/Domain/Entities/RefreshToken.cs (1 hunks)
  • src/Infrastructure/Data/Configurations/RefreshTokenConfiguration.cs (1 hunks)
  • src/Infrastructure/Data/Contexts/DataContext.cs (1 hunks)
  • src/Infrastructure/Data/Migrations/20251121160401_CreateRefreshTokenTable.Designer.cs (1 hunks)
  • src/Infrastructure/Data/Migrations/20251121160401_CreateRefreshTokenTable.cs (1 hunks)
  • src/Infrastructure/Data/Migrations/DataContextModelSnapshot.cs (2 hunks)
  • src/Infrastructure/Extensions/ServiceCollectionExtensions.cs (1 hunks)
  • src/Infrastructure/Repositories/RefreshTokenRepository.cs (1 hunks)
  • src/Infrastructure/Services/TokenService.cs (2 hunks)
  • src/Web.Api/Controllers/V1/AuthController.cs (3 hunks)
  • src/Web.Api/Middleware/GlobalExceptionMiddleware.cs (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (17)
src/Infrastructure/Extensions/ServiceCollectionExtensions.cs (1)
src/Infrastructure/Repositories/RefreshTokenRepository.cs (2)
  • RefreshTokenRepository (11-77)
  • RefreshTokenRepository (15-18)
src/Infrastructure/Data/Contexts/DataContext.cs (2)
src/Domain/Constants/TokenConstants.cs (1)
  • RefreshToken (11-22)
src/Domain/Entities/RefreshToken.cs (2)
  • RefreshToken (8-82)
  • RefreshToken (62-71)
src/Application/Interfaces/Repositories/IRefreshTokenRepository.cs (3)
src/Domain/Constants/TokenConstants.cs (1)
  • RefreshToken (11-22)
src/Domain/Entities/RefreshToken.cs (2)
  • RefreshToken (8-82)
  • RefreshToken (62-71)
src/Infrastructure/Repositories/RefreshTokenRepository.cs (1)
  • Update (39-42)
src/Domain/Constants/TokenConstants.cs (1)
src/Domain/Entities/RefreshToken.cs (2)
  • RefreshToken (8-82)
  • RefreshToken (62-71)
src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommandHandler.cs (5)
src/Application/Interfaces/Repositories/IRefreshTokenRepository.cs (6)
  • Task (13-13)
  • Task (18-18)
  • Task (23-23)
  • Task (33-33)
  • Task (38-38)
  • Task (43-43)
src/Domain/Constants/TokenConstants.cs (3)
  • RefreshToken (11-22)
  • TokenConstants (6-54)
  • RevocationReasons (27-53)
src/Domain/Entities/RefreshToken.cs (3)
  • RefreshToken (8-82)
  • RefreshToken (62-71)
  • Revoke (76-81)
src/Application/Common/Exceptions/SecurityException.cs (3)
  • SecurityException (7-16)
  • SecurityException (9-11)
  • SecurityException (13-15)
src/Application/Interfaces/Services/Auth/ITokenService.cs (1)
  • GenerateRefreshToken (18-18)
src/Domain/Entities/RefreshToken.cs (1)
src/Domain/Constants/TokenConstants.cs (1)
  • RefreshToken (11-22)
src/Web.Api/Middleware/GlobalExceptionMiddleware.cs (1)
src/Application/Common/Exceptions/SecurityException.cs (3)
  • SecurityException (7-16)
  • SecurityException (9-11)
  • SecurityException (13-15)
src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommandValidator.cs (2)
src/Domain/Constants/TokenConstants.cs (1)
  • RefreshToken (11-22)
src/Domain/Entities/RefreshToken.cs (2)
  • RefreshToken (8-82)
  • RefreshToken (62-71)
src/Infrastructure/Data/Configurations/RefreshTokenConfiguration.cs (3)
src/Domain/Constants/TokenConstants.cs (1)
  • RefreshToken (11-22)
src/Domain/Entities/RefreshToken.cs (2)
  • RefreshToken (8-82)
  • RefreshToken (62-71)
src/Infrastructure/Data/Contexts/Schemas.cs (1)
  • Schemas (3-7)
src/Application/Interfaces/Services/Auth/ITokenService.cs (1)
src/Infrastructure/Services/TokenService.cs (2)
  • accessToken (30-37)
  • GenerateAccessToken (93-131)
src/Application/Features/Auth/LoginExternal/LoginExternalCommandHandler.cs (5)
src/Application/Interfaces/Repositories/IRefreshTokenRepository.cs (6)
  • Task (13-13)
  • Task (18-18)
  • Task (23-23)
  • Task (33-33)
  • Task (38-38)
  • Task (43-43)
src/Infrastructure/Repositories/RefreshTokenRepository.cs (4)
  • Task (20-23)
  • Task (25-30)
  • Task (32-37)
  • Task (44-52)
src/Application/Interfaces/Services/Auth/ITokenService.cs (3)
  • Task (23-23)
  • accessToken (13-13)
  • GenerateRefreshToken (18-18)
src/Domain/Entities/RefreshToken.cs (2)
  • RefreshToken (8-82)
  • RefreshToken (62-71)
src/Domain/Entities/User.cs (1)
  • User (8-42)
src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommand.cs (2)
src/Domain/Constants/TokenConstants.cs (1)
  • RefreshToken (11-22)
src/Domain/Entities/RefreshToken.cs (2)
  • RefreshToken (8-82)
  • RefreshToken (62-71)
src/Application/Features/Auth/Login/LoginCommandHandler.cs (4)
src/Domain/Constants/TokenConstants.cs (2)
  • TokenConstants (6-54)
  • RefreshToken (11-22)
src/Domain/Entities/RefreshToken.cs (2)
  • RefreshToken (8-82)
  • RefreshToken (62-71)
src/Application/Interfaces/Services/Auth/ITokenService.cs (2)
  • accessToken (13-13)
  • GenerateRefreshToken (18-18)
src/Infrastructure/Services/TokenService.cs (3)
  • accessToken (30-37)
  • GenerateAccessToken (93-131)
  • GenerateRefreshToken (42-48)
src/Infrastructure/Data/Migrations/20251121160401_CreateRefreshTokenTable.Designer.cs (1)
src/Infrastructure/Data/Migrations/20251121160401_CreateRefreshTokenTable.cs (1)
  • CreateRefreshTokenTable (9-77)
src/Application/Features/Auth/Register/RegisterResponse.cs (1)
src/Web.Api/Services/CurrentUserService.cs (1)
  • List (58-90)
src/Web.Api/Controllers/V1/AuthController.cs (4)
src/Application/Interfaces/Services/Auth/ITokenService.cs (1)
  • Task (23-23)
src/Infrastructure/Services/TokenService.cs (1)
  • Task (50-53)
src/Domain/Constants/TokenConstants.cs (1)
  • RefreshToken (11-22)
src/Domain/Entities/RefreshToken.cs (2)
  • RefreshToken (8-82)
  • RefreshToken (62-71)
src/Infrastructure/Services/TokenService.cs (1)
src/Application/Interfaces/Services/Auth/ITokenService.cs (1)
  • accessToken (13-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-deploy
🔇 Additional comments (17)
src/Application/Features/Auth/Login/LoginCommand.cs (1)

33-51: Clear separation of access/refresh expirations in LoginResponse

Exposing both AccessTokenExpiresAt (short‑lived) and RefreshTokenExpiresAt (RememberMe‑dependent) makes the contract explicit and should simplify client-side token management and UX around session duration.

src/Infrastructure/Extensions/ServiceCollectionExtensions.cs (1)

82-95: Refresh token repository registration is correctly wired

IRefreshTokenRepository mapped to RefreshTokenRepository as scoped aligns with the DataContext lifetime and should integrate cleanly with the new refresh-token workflows.

src/Infrastructure/Data/Contexts/DataContext.cs (1)

29-35: Expose RefreshTokens DbSet is correct and consistent

Adding DbSet<RefreshToken> RefreshTokens => Set<RefreshToken>(); aligns with the existing pattern for domain entities and is required for EF Core configuration/migrations to work. No issues from a correctness or design standpoint.

src/Web.Api/Middleware/GlobalExceptionMiddleware.cs (1)

102-110: SecurityException mapping to 401 with structured error looks good

Handling SecurityException as 401 Unauthorized with a typed error payload (Type = "SecurityError", Details from the exception) is consistent with the existing pattern for custom exceptions and will make client handling straightforward.

src/Infrastructure/Data/Configurations/RefreshTokenConfiguration.cs (1)

13-46: RefreshToken EF configuration is well‑structured

Table mapping, key, property constraints, indexes, and the soft‑delete query filter all look appropriate for the refresh‑token use case (lookup by token, user‑scoped queries, and cleanup by expiry). The relationship to User with cascade delete plus the filter on !rt.User.IsDeleted is consistent with a soft‑delete model.

No issues from an EF Core or schema‑design standpoint.

src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommand.cs (1)

11-17: Well-structured command/response for refresh flow

Command and response contracts look solid: required init-only properties, optional rotated RefreshToken, explicit AccessTokenExpiresAt/RefreshTokenExpiresAt, and UserInfo payload all align well with a secure refresh-token flow.

Also applies to: 22-47

src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommandValidator.cs (1)

10-17: Validator covers essential constraints on refresh token

Using NotEmpty() plus a minimum length is an appropriate, lightweight guard for the refresh token input here; this should integrate cleanly with the CQRS pipeline.

src/Application/Features/Auth/LoginExternal/LoginExternalCommandHandler.cs (1)

22-28: Good refactor: shared token generation + remember‑me support

Consolidating token generation into GenerateTokenResponse and threading rememberMe through all three login paths keeps the external login flow consistent and makes the new refresh‑token behavior easy to maintain. The new IRefreshTokenRepository dependency and use of the domain RefreshToken entity also fit well with the existing UoW/repository pattern.

Also applies to: 80-81, 113-114, 174-185

src/Infrastructure/Data/Migrations/20251121160401_CreateRefreshTokenTable.cs (1)

14-42: RefreshTokens table schema and indexes look appropriate

The table definition aligns with the RefreshToken domain model, and the chosen indexes (by ExpiresAt, unique Token, UserId, and (UserId, RevokedAt)) should support common queries (lookup by token, active tokens by user, cleanup) efficiently.

Also applies to: 44-68, 73-76

src/Infrastructure/Data/Migrations/DataContextModelSnapshot.cs (1)

150-205: Model snapshot aligns with RefreshToken schema and indexing

The RefreshToken model in the snapshot (including IsPersistent, constrained RevokedReason/Token lengths, and the added indexes on ExpiresAt, Token (unique), and (UserId, RevokedAt)) is consistent with the new migration and supports the intended refresh‑token workflows.

src/Application/Features/Auth/RefreshAccessToken/RefreshAccessTokenCommandHandler.cs (1)

30-54: Token validation logic is sound; just ensure the repository eagerly loads the User navigation

The sequence of checks (invalid → revoked → expired → missing user) is reasonable and uses appropriate exception types. One thing to verify: storedRefreshToken.User is assumed to be populated. Ensure IRefreshTokenRepository.GetByTokenAsync includes the related User (and its Roles) to avoid null navigation access at this point.

src/Application/Features/Auth/Login/LoginCommandHandler.cs (2)

40-44: Refresh token expiry calculation is consistent and clear

Using TokenConstants.RefreshToken.NormalExpiration vs RememberMeExpiration based on request.RememberMe is straightforward and aligns with the domain constants. This keeps the expiry policy centralized and easy to adjust.


73-80: Updated response surface is coherent with new token model

Returning AccessToken, RefreshToken, and both expiration timestamps alongside User makes the client contract explicit and aligns with the new token lifecycle. No issues here.

src/Domain/Entities/RefreshToken.cs (1)

8-81: Entity design matches the intended refresh‑token lifecycle

The properties and helpers (Create, Revoke, IsExpired, IsActive, IsPersistent, ReplacedByToken) provide exactly what the application layer needs for rotation and chain revocation. Using replacedByToken ?? Token in Revoke also keeps the chain well‑defined even when there is no successor token.

No issues from a domain‑model standpoint.

src/Infrastructure/Data/Migrations/20251121160401_CreateRefreshTokenTable.Designer.cs (1)

153-207: RefreshTokens mapping and indexes align with the domain model

The EF model for Domain.Entities.RefreshToken (columns plus indexes on Token, ExpiresAt, UserId, and (UserId, RevokedAt)) matches the domain entity and the Up migration. This should support efficient lookup by token and user for the flows introduced in this PR.

src/Infrastructure/Repositories/RefreshTokenRepository.cs (1)

20-23: Repository write methods rely on outer SaveChanges – verify call sites

AddAsync, Update, RevokeAsync, RevokeAllForUserAsync, and CleanExpiredTokensAsync all mutate the DbContext but never call SaveChanges/SaveChangesAsync. That’s fine with a unit-of-work pattern, but if any caller (especially background jobs running CleanExpiredTokensAsync) forgets to commit, changes will be silently discarded. Please double‑check that every code path using these methods always persists the context afterward.

Also applies to: 39-42, 44-62, 64-76

src/Infrastructure/Services/TokenService.cs (1)

50-53: GetClaimsFromTokenAsync is incomplete but not a current blocker for auth flows

Verification shows that while GetClaimsFromTokenAsync throws NotImplementedException, it is not currently called by any active code path. The refresh-token flow in RefreshAccessTokenCommandHandler and login flows in LoginCommandHandler only call GenerateAccessToken() and GenerateRefreshToken(). User claims are loaded from stored refresh-token entities, not from JWT claims extraction. Since no code currently depends on this method, it does not block existing auth workflows.

However, implementing the method is still sensible for interface completeness. The ValidateToken method exists and returns the required ClaimsPrincipal?, supporting the suggested implementation approach.

Comment thread src/Infrastructure/Services/TokenService.cs
@taiphanvan2k3 taiphanvan2k3 merged commit f3ca946 into main Nov 21, 2025
3 checks passed
@taiphanvan2k3 taiphanvan2k3 deleted the feat/handle-remember-me-and-refresh-token branch November 21, 2025 16:29
taiphanvan2k3 added a commit that referenced this pull request Dec 22, 2025
…me-and-refresh-token

feat: Implement refresh token functionality and enhance authentication
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant