Hi,
ASP.NET Zero API: v13.3.0 | Client: v13.3.0
Goal: Allow an operator to force another operator to be logged out, lock their account, expire their session, etc.
I tried adding an API to TokenAuthController.cs. I tried the following methods without success. I think I got misunderstanding.
- remove the user from userSettingCache
- Invalidate or remove SecurityStampKey, TokenValidityKey and RefreshTokenValidityKey
- deleting the tokens from the [AbpUserTokens] repository
- attempt to remove access and refresh tokens from the user's claim, but _userManager.GetClaimsAsync(user) returns nothing
Please advise a proper way to resolve the issue.
[HttpPost]
[AbpAuthorize(AppPermissions.Pages_Administration_Users)]
public async Task<IActionResult> ForceLogout(long userId)
{
var user = await _userManager.FindByIdAsync(userId.ToString());
if (user == null)
{
return NotFound("User not found.");
}
// Here, implement logic to revoke the user's token and refresh token
// Invalidate or remove any tokens associated with the user,
//var userIdentifier = new UserIdentifier(AbpSession.TenantId, userId);
//var userSettingCache = _cacheManager.GetUserSettingsCache();
//await userSettingCache.RemoveAsync(userIdentifier.ToString());
// Invalidate SecurityStampKey
var securityStampCache = _cacheManager.GetCache(AppConsts.SecurityStampKey);
await securityStampCache.RemoveAsync(userId.ToString());
// Invalidate TokenValidityKey
var tokenValidityCache = _cacheManager.GetCache(AppConsts.TokenValidityKey);
await tokenValidityCache.RemoveAsync(userId.ToString());
// Invalidate RefreshTokenValidityKey
var refreshTokenValidityCache = _cacheManager.GetCache(AppConsts.RefreshTokenValidityKey);
await refreshTokenValidityCache.RemoveAsync(userId.ToString());
// userClaims is empty
#if false var userClaims = await _userManager.GetClaimsAsync(user); if (userClaims.Any()) { var tokenValidityKeyInClaims = user.Claims.First(c => c.ClaimType == AppConsts.TokenValidityKey); if (tokenValidityKeyInClaims != null) { await RemoveTokenAsync(tokenValidityKeyInClaims.ClaimValue); }
var refreshTokenValidityKeyInClaims =
user.Claims.FirstOrDefault(c => c.ClaimType == AppConsts.RefreshTokenValidityKey);
if (refreshTokenValidityKeyInClaims != null)
{
await RemoveTokenAsync(refreshTokenValidityKeyInClaims.ClaimValue);
}
}
#endif
#if false // deleting the tokens from the [AbpUserTokens] table isn't forcing the user to log out
// Find all tokens associated with the user
var userTokens = await _userTokenRepository.GetAllListAsync(t => t.UserId == userId);
// Delete tokens to force logout
foreach (var token in userTokens)
{
await _userTokenRepository.DeleteAsync(token);
}
#endif
// Update security stamp to invalidate user's current tokens
// If using ASP.NET Identity, you can invalidate the user's token by updating their SecurityStamp.
// This will force the token to be invalid for future requests.
//await _userManager.UpdateSecurityStampAsync(user);
return Ok("User has been logged out.");
}
7 Answer(s)
-
0
Hi hongbing.wang
Could you please repeat the following ForceLogout
[HttpPost] [AbpAuthorize(AppPermissions.Pages_Administration_Users)] public async Task<IActionResult> ForceLogout(int tenantId, long userId) { var user = await _userManager.FindByIdAsync(userId.ToString()); if (user == null) { return NotFound("User not found."); } // Update security stamp to invalidate user's current tokens await _userManager.UpdateSecurityStampAsync(user); // Delete tokens from AbpUserTokens repository var userTokens = await _userTokenRepository.GetAllListAsync(t => t.UserId == userId); foreach (var token in userTokens) { await _userTokenRepository.DeleteAsync(token); } // Clear SecurityStampKey cache var securityStampCache = _cacheManager.GetCache(AppConsts.SecurityStampKey); await securityStampCache.RemoveAsync(userId.ToString()); // Clear TokenValidityKey cache var tokenValidityCache = _cacheManager.GetCache(AppConsts.TokenValidityKey); await tokenValidityCache.RemoveAsync(userId.ToString()); // Clear RefreshTokenValidityKey cache var refreshTokenValidityCache = _cacheManager.GetCache(AppConsts.RefreshTokenValidityKey); await refreshTokenValidityCache.RemoveAsync(userId.ToString()); // Remove security stamp cache item for the user await _securityStampHandler.RemoveSecurityStampCacheItem(tenantId, userId); return Ok("User has been logged out."); }
-
0
Hi @oguzhanagir,
It works. Thank you so much.
-
0
Hi @oguzhanagir,
The method ForceLogout in TokenAuthController.cs works fine.
I tried to call the method from UserAppService using the following code. It makes a request https://localhost:44301/api/TokenAuth/ForceLogout?tenantId=1&userId=5 to TokenAuthController. It didn't work. However, the same request works in Swagger UI. What did I miss? Should I use a different approach?
[AbpAuthorize(AppPermsExtension.Pages_Administration_Users_ForceLogout)] public async Task ForceLogout(EntityDto<long> input) { var user = await UserManager.GetUserByIdAsync(input.Id); var client = _httpClientFactory.CreateClient(); // Create ActionContext manually from HttpContext var actionContext = new ActionContext( _httpContextAccessor.HttpContext, _httpContextAccessor.HttpContext.GetRouteData(), new Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor() ); // Create IUrlHelper instance var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext); // Generate relative URL for ForceLogout endpoint var tenantId = user.TenantId; var userId = user.Id; var relativeUrl = urlHelper.Action("ForceLogout", "TokenAuth", new { tenantId, userId }); // Get base URL from HttpContext var request = _httpContextAccessor.HttpContext.Request; var baseUrl = $"{request.Scheme}://{request.Host}"; // Combine base URL with relative URL var absoluteUrl = new Uri(new Uri(baseUrl), relativeUrl); // Make the request to TokenAuthController var response = await client.PostAsync(absoluteUrl, null); response.EnsureSuccessStatusCode(); var uid = UidHelper.BuildDeviceUid(DeviceType.Operator, 0, (int)input.Id); await RaiseActionEventAsync(ControlCommand.ForceLogout, uid); }
-
0
Hi hongbing.wang
Can you share with us how you call the AppService method in TokenAuthController? When you put a breakpoint in the app service method in debug mode, does it go into the AppService method?
-
0
Yes, it goes into the AppService method ForceLogout(EntityDto<long> input).
The response of 'await client.PostAsync(absoluteUrl, null);' is: {StatusCode: 404, ReasonPhrase: 'Not Found', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:{ Date: Mon, 04 Nov 2024 06:20:26 GMT X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Content-Length: 0}, Trailing Headers:{}}
The request: {Method: GET, RequestUri: 'https://localhost:44301/Account/Login?ReturnUrl=%2Fapi%2FTokenAuth%2FForceLogout%3FtenantId%3D1%26userId%3D5', Version: 1.1, Content: <null>, Headers:{ traceparent: 00-7a6f3b19c73036d131fac966f698dc22-0ba3db0c2fd75929-00}}
The absoluteUrl is https://localhost:44301/api/TokenAuth/ForceLogout?tenantId=1&userId=5 but the requestUri is {https://localhost:44301/Account/Login?ReturnUrl=%2Fapi%2FTokenAuth%2FForceLogout%3FtenantId%3D1%26userId%3D5}?
-
0
Hi
Here, you need to correct the ForceLogout operation so that it can be used by the UserAppService and the TokenAuthController, as it will be in common use. You can take this approach instead of sending a request to TokenAuthController through the AppService method.
-
0
The direct approach in UserAppService works. All the porting of the code was straightforward. I only needed to write a line of code to replace "await _securityStampHandler.RemoveSecurityStampCacheItem(tenantId, userId);"