Base solution for your next web application
Open Closed

Concurrent Session #12606


User avatar
0
smartech created

We were testing concurrent session feature , we need to return a specific error code for the old session to notify the user that another session was opened so user will know if others can access the account and can change the password, how can we do?

Markdown is supported
Copy & paste or drag & drop images (max 30 MB per image)

1 Answer(s)
  • User Avatar
    0
    oguzhanagir created
    Support Team

    Hi @smartech

    To notify a user that they have been logged out because of a new session, we utilize the Security Stamp mechanism. When a new login occurs, the stamp is refreshed, making the old JWT token invalid.

    Define the Error Code

    Create a constant for the error code. This allows the frontend to identify the error programmatically without relying on string matching localized text.

    namespace MyCompanyName.AbpZeroTemplate.Authorization
    {
        public static class AbpZeroTemplateErrorCodes
        {
            public const string ConcurrentSessionDetected = "ConcurrentSession";
        }
    }
    
    

    Update the JWT Security Stamp Handler

    Ensure your JwtSecurityStampHandler.cs is correctly validating the stamp against the cache or database. This method should return true if the session is still valid.

    public async Task<bool> Validate(ClaimsPrincipal claimsPrincipal)
    {
        if (claimsPrincipal?.Claims == null || !claimsPrincipal.Claims.Any())
        {
            return false;
        }
    
        var securityStampKey = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == AppConsts.SecurityStampKey);
        if (securityStampKey == null)
        {
            return false;
        }
    
        var userIdentifierString = claimsPrincipal.Claims.First(c => c.Type == AppConsts.UserIdentifier);
        var userIdentifier = UserIdentifier.Parse(userIdentifierString.Value);
    
        var isValid = await ValidateSecurityStampFromCache(userIdentifier, securityStampKey.Value);
        if (!isValid)
        {
            isValid = await ValidateSecurityStampFromDb(userIdentifier, securityStampKey.Value);
        }
    
        return isValid;
    }
    
    

    Throw the Exception in the Token Handler

    In AbpZeroTemplateAsyncJwtSecurityTokenHandler.cs, we intercept the validation result. If it returns false, we throw a BusinessException.

    private static async Task ValidateSecurityStampAsync(ClaimsPrincipal principal)
    {
        ValidateUserDelegation(principal);
    
        using (var securityStampHandler = IocManager.Instance.ResolveAsDisposable<IJwtSecurityStampHandler>())
        {
            if (!await securityStampHandler.Object.Validate(principal))
            {
                throw new BusinessException(AbpZeroTemplateErrorCodes.ConcurrentSessionDetected)
                {
                    Severity = LogSeverity.Warn
                };
            }
        }
    }
    
    

    If you want a simpler approach that includes a built in message, you can create a custom exception class:

    public class ConcurrentSessionException : UserFriendlyException
    {
        public ConcurrentSessionException() 
            : base("ConcurrentSessionDetected", "Your session has been terminated due to a new login from another location.")
        {
        }
        
        public ConcurrentSessionException(string message) 
            : base("ConcurrentSessionDetected", message)
        {
        }
    }
    
    

    Expected API Response Format

    When the exception is thrown, the ABP Framework automatically wraps the result. Your frontend will receive a 500 or 403 status with the following JSON body:

    {
      "result": null,
      "targetUrl": null,
      "success": false,
      "error": {
        "code": 0,
        "message": "ConcurrentSessionDetected",
        "details": "Your session has been terminated due to a new login from another location.",
        "validationErrors": null
      },
      "unAuthorizedRequest": false,
      "__abp": true
    }
    
    

    Handling the Error in Angular

    In your AppHttpInterceptor.ts (or your primary HTTP interceptor), you can catch this specific message and redirect the user to the login page with a warning.

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(request).pipe(
            catchError(error => {
                if (error.error?.error?.message === 'ConcurrentSessionDetected') {
                    abp.message.warn('A new login was detected from another device. Please log in again.');
                    
                    this._router.navigate(['/account/login']);
                }
                return throwError(error);
            })
        );
    }
    
    
    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)