[$250] Session expired but user was not signed out
If you havenโt already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!
Version Number: 9.2.43-0 Reproducible in staging?: Needs Reproduction (Unable to get session expired message) Reproducible in production?: Needs Reproduction If this was caught during regression testing, add the test name, ID and link from TestRail: Email or phone of affected tester (no customers): Logs: https://stackoverflow.com/c/expensify/questions/4856 Expensify/Expensify Issue URL: Issue reported by: @cead22 @coleaeason Slack conversation (hyperlinked to channel name): #Expensify Bugs
Action Performed:
- Log into staging web
- Wait until the session expires in the background
- Attempt any action (e.g., sending a message or opening a report)
- Observe that the request fails, but the user remains logged in
Expected Result:
Once the session expires, the app should automatically sign the user out or redirect them to the login screen with a clear message that the session has expired.
Actual Result:
The user remains on the app with an expired session and is not redirected to sign in again.
Workaround:
Unknown
Platforms:
Select the officially supported platforms where the issue was reproduced:
- [ ] Android: App
- [ ] Android: mWeb Chrome
- [ ] iOS: App
- [ ] iOS: mWeb Safari
- [ ] iOS: mWeb Chrome
- [ ] Windows: Chrome
- [x] MacOS: Chrome / Safari
- [ ] MacOS: Desktop
Platforms Tested:
On which of our officially supported platforms was this issue tested:- [ ] Android: App
- [ ] Android: mWeb Chrome
- [ ] iOS: App
- [ ] iOS: mWeb Safari
- [ ] iOS: mWeb Chrome
- [x] Windows: Chrome
- [ ] MacOS: Chrome / Safari
- [ ] MacOS: Desktop
Screenshots/Videos
Add any screenshot/video evidence
The logs and screenshot for the issue is in OP
Upwork Automation - Do Not Edit
- Upwork Job URL: https://www.upwork.com/jobs/~021986432883694552860
- Upwork Job ID: 1986432883694552860
- Last Price Increase: 2025-12-25
Issue Owner
Current Issue Owner: @abzokhattab
Triggered auto assignment to @dylanexpensify (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.
Job added to Upwork: https://www.upwork.com/jobs/~021986432883694552860
Triggered auto assignment to Contributor-plus team member for initial proposal review - @abzokhattab (External)
Proposal
Why this happens
When a userโs authentication session/token expires, the UI still shows them as logged in, but all requests fail in the background. This results in a broken user state.
Root Cause
The frontend is not listening for:
- HTTP 401 Unauthorized responses or
- Token expiration time
If you want, I can provide solution code for your frontend and backend rules.
๐ฃ @rodrigocost! ๐ฃ Hey, it seems we donโt have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork. Please follow these steps:
- Make sure you've read and understood the contributing guidelines.
- Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
- Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
- Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
Format:
Contributor details
Your Expensify account email: <REPLACE EMAIL HERE>
Upwork Profile Link: <REPLACE LINK HERE>
โ ๏ธ @rodrigocost Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).
Proposal
Please re-state the problem that we are trying to solve in this issue.
Session expired but user was not signed out
Expected Result: Once the session expires, the app should automatically sign the user out or redirect them to the login screen with a clear message that the session has expired.
Actual Result: The user remains on the app with an expired session and is not redirected to sign in again.
What is the root cause of that problem?
The root cause has two parts:
-
Missing redirect when reauthentication fails silently: In
Reauthentication.tsmiddleware, when reauthentication fails (wasSuccessfulisfalse), the middleware returns early without ensuring the request is properly resolved. WhileredirectToSignIn()should have been called inAuthentication.tswhen reauthentication fails, there's no guarantee it was called in all failure scenarios. -
Missing redirect when reauthentication returns no response: In
Authentication.ts, when the reauthentication request returnsnullorundefined(which can happen when the session has expired and credentials are invalid), the function returnsfalsewithout callingredirectToSignIn(). This leaves the user in a logged-in state even though their session has expired.
What changes do you think we should make in order to solve the problem?
-
Fix
Reauthentication.tsmiddleware to properly handle reauthentication failures:- When
wasSuccessfulisfalse, ensure the request is properly resolved - Add comments explaining that
redirectToSignIn()should have been called inAuthentication.ts, but we need to ensure the request is handled correctly - This ensures that even if reauthentication fails, the request doesn't hang and the user experience is consistent
- When
-
Fix
Authentication.tsto redirect when reauthentication returns no response:- When the reauthentication response is
nullorundefined, callredirectToSignIn('session.expired')before returningfalse - This ensures that when the session has expired and credentials are invalid, the user is properly signed out and redirected to the login screen
- Set
isAuthenticatingtofalseto allow the app to process the redirect
- When the reauthentication response is
These changes ensure that:
- When a session expires and reauthentication fails, the user is always signed out
- The user is redirected to the login screen with an appropriate error message
- The error message "Your session has expired" is displayed on the sign-in page
Code Changes
1. src/libs/Middleware/Reauthentication.ts
Before:
return reauthenticate(request?.commandName)
.then((wasSuccessful) => {
if (!wasSuccessful) {
return;
}
if (isFromSequentialQueue || apiRequestType === CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS) {
return processWithMiddleware(request, isFromSequentialQueue);
}
if (apiRequestType === CONST.API_REQUEST_TYPE.READ) {
NetworkConnection.triggerReconnectionCallbacks('read request made with expired authToken');
return Promise.resolve();
}
replayMainQueue(request);
})
After:
return reauthenticate(request?.commandName)
.then((wasSuccessful) => {
if (!wasSuccessful) {
// Reauthentication failed - redirectToSignIn should have been called in Authentication.ts,
// but we need to ensure the request is properly resolved and the user is signed out.
// If we're here, it means the session has expired and we couldn't reauthenticate.
if (isFromSequentialQueue) {
return data;
}
if (request.resolve) {
request.resolve(data);
}
return data;
}
if (isFromSequentialQueue || apiRequestType === CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS) {
return processWithMiddleware(request, isFromSequentialQueue);
}
if (apiRequestType === CONST.API_REQUEST_TYPE.READ) {
NetworkConnection.triggerReconnectionCallbacks('read request made with expired authToken');
return Promise.resolve();
}
replayMainQueue(request);
})
Changes:
- Added proper handling when
wasSuccessfulisfalse - Ensures the request is properly resolved when reauthentication fails
- Added comments explaining the behavior and why we need to handle this case
- Returns the original
data(which contains theNOT_AUTHENTICATEDerror) so the request is properly resolved
2. src/libs/Authentication.ts
Before:
}).then((response) => {
if (!response) {
return false;
}
Log.hmmm('Reauthenticate - Processing authentication result', {
command,
});
if (response.jsonCode === CONST.JSON_CODE.UNABLE_TO_RETRY) {
// When a fetch() fails due to a network issue and an error is thrown we won't log the user out. Most likely they
// have a spotty connection and will need to retry reauthenticate when they come back online. Error so it can be handled by the retry mechanism.
throw new Error('Unable to retry Authenticate request');
}
// If authentication fails and we are online then log the user out
if (response.jsonCode !== 200) {
const errorMessage = getAuthenticateErrorMessage(response);
setIsAuthenticating(false);
Log.hmmm('Redirecting to Sign In because we failed to reauthenticate', {
command,
error: errorMessage,
});
redirectToSignIn(errorMessage);
return false;
}
After:
}).then((response) => {
if (!response) {
// If we get no response, the reauthentication request failed completely.
// This could happen if the session has expired and credentials are invalid.
setIsAuthenticating(false);
Log.hmmm('Redirecting to Sign In because reauthentication returned no response', {
command,
});
redirectToSignIn('session.expired');
return false;
}
Log.hmmm('Reauthenticate - Processing authentication result', {
command,
});
if (response.jsonCode === CONST.JSON_CODE.UNABLE_TO_RETRY) {
// When a fetch() fails due to a network issue and an error is thrown we won't log the user out. Most likely they
// have a spotty connection and will need to retry reauthenticate when they come back online. Error so it can be handled by the retry mechanism.
throw new Error('Unable to retry Authenticate request');
}
// If authentication fails and we are online then log the user out
if (response.jsonCode !== 200) {
const errorMessage = getAuthenticateErrorMessage(response);
setIsAuthenticating(false);
Log.hmmm('Redirecting to Sign In because we failed to reauthenticate', {
command,
error: errorMessage,
});
redirectToSignIn(errorMessage);
return false;
}
Changes:
- Added handling for when
responseisnullorundefined - Calls
redirectToSignIn('session.expired')to sign out the user and redirect to login - Sets
isAuthenticatingtofalseto allow the app to process the redirect - Added comments explaining why this case needs to be handled
- Uses the translation key
'session.expired'which displays "Your session has expired." on the sign-in page
What alternative solutions did you explore? (Optional)
-
Adding a session expiration check on every API request: We could check if the session is expired before making API requests, but this would add overhead and the server already handles this by returning
NOT_AUTHENTICATED. The current approach of handling it in the middleware is more efficient.
Hi @IjaazA I am struggling reproducing the issue... are u able to reproduce it?
๐ฃ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? ๐ธ
@abzokhattab it is not reproducible now , I faced session expiry twice or thrice and took logs and with that i drafted rc and solution , but in recent main now I can't
Can you please retest @cead22 @coleaeason
I didn't experienced this today when coming back to new dot after a period of inactivity
@abzokhattab this just happened to me again. Let me know what info I can get to help troubleshoot. I shared some data in slack
๐ฃ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? ๐ธ
bumped on Slack for proposals :eyes: https://expensify.slack.com/archives/C01GTK53T8Q/p1763991419545099
๐ฃ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? ๐ธ
@abzokhattab Whoops! This issue is 2 days overdue. Let's get this updated quick!
@abzokhattab @dylanexpensify this issue is now 4 weeks old, please consider:
- Finding a contributor to fix the bug
- Closing the issue if BZ has been unable to add the issue to a VIP or Wave project
- If you have any questions, don't hesitate to start a discussion in #expensify-open-source
Thanks!
@abzokhattab Now this issue is 8 days overdue. Are you sure this should be a Daily? Feel free to change it!
๐ฃ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? ๐ธ
@abzokhattab 10 days overdue. I'm getting more depressed than Marvin.
Issue not reproducible during KI retests. (First week)
๐ฃ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? ๐ธ
This issue has not been updated in over 14 days. @abzokhattab, @dylanexpensify eroding to Weekly issue.
๐ฃ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? ๐ธ
๐ฃ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? ๐ธ
๐ฃ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? ๐ธ
No decision yet ... there is still an active conversation on slack https://expensify.slack.com/archives/C01GTK53T8Q/p1763571739910709
cc @cead22 @VickyStash
Not so long ago Jason also had similar issue (see this thread). I've tried to reproduce the problem on my end, but had no luck. @cead22 Does it still regularly happen to you?