Hi,
ASP.NET Zero API: v13.3.0 | Angular client: v13.3.0
We want to shorten AccessTokenExipration to 15 minutes and RefreshTokenExpiration to 7 days.
The API Task<RefreshTokenResult> RefreshToken(string refreshToken) works fine in Swagger UI. However, there are following issues with the Angular client.
When the access token expires, if customer tries to open any page that is not on the main tree, it gives an internal error, instead of re-routing to the login page.
For example:
Go to Departments page and wait 15 minutes
Go to Custom fields page and get Internal error
Go to Controllers page and get Internal error
Go to Alarms page and now it redirects you to the login page
More importantly, RefreshToken is not called or not working in the client.
Has the RefreshToken() been fully implemented in the client? If not, please advise the method for the implementation.
We are aware that there is "this._tokenAuthService.refreshToken(token)" in tryAuthWithRefreshToken() in "src\account\auth\zero-refresh-token.service.ts". this._refreshTokenService.tryAuthWithRefreshToken() is called by canActivateInternal in "src\app\shared\common\auth\auth-route-guard.ts". But we need further assistance.
Hi İsmail, I have shared the original Zero 13.3 project with [email protected]
Hi İsmail,
Here is the video recorded from the original default Zero app V13.3.0. It doesn’t meet the following requirement: If the Operator successfully logs in with the remember browser option ticked, then they should not be prompted to enter 2FA again until they go to another browser.
Please investigate the issue.
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);"
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}?
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);
}
Hi @oguzhanagir,
It works. Thank you so much.
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.
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.");
}
Hi @m.aliozkaya,
Thank you for the update.
I do have 'Remember this browser' ticked.
I also have TwoFactoRememberClientToken in angular local storage.
However, if I logout and then login again within the same browser. I am still required to provide two factor access code from Google Authenticator. Is this normal? Please explain how this should work. Thank you.
Hi @ismcagdas,
ASP.NET Zero API: v13.3.0 | Client: v13.3.0
Goal: Add an option to remember browser for Operators with MFA enabled. When selected, Operators can choose to remember browser, which will skip MFA prompts for future logins from the same browser.”
We enabled 2FA settings on settings page and also for specific user. 2FA works.
We also added an option "Allow to remember browser" on settings page [Administration > Settings > Security (tab)]. See the screenshot below.
We checked the setting is updated in TenantSettingsAppService.cs, settings.IsRememberBrowserEnabled = true, but users are still prompted for additional authentication (two-factor authentication) on a "remembered" browser.
Did I miss something? How the "remember browser" functionality is being implemented? Does the "Remember this browser" functionality relies on a cookie being set in the user's browser when they select the option to remember the device. I couldn't find such a cookie in the browser.
Do we need to check the host settings (HostSettingsAppService) do not force 2FA?
Thank you for your support.