Documentation Index Fetch the complete documentation index at: https://mintlify.com/Expensify/App/llms.txt
Use this file to discover all available pages before exploring further.
Overview
New Expensify uses token-based authentication with automatic session management and reauthentication.
Authentication Flow
Session Management
Session Storage
Session data is stored in Onyx:
import ONYXKEYS from '@src/ONYXKEYS' ;
// Session structure
type Session = {
authToken : string ; // Authentication token
accountID : number ; // User's account ID
email : string ; // User's email
encryptedAuthToken ?: string ; // For sensitive operations
};
// Stored at
ONYXKEYS . SESSION
Getting Session Data
import { useOnyx } from 'react-native-onyx' ;
import ONYXKEYS from '@src/ONYXKEYS' ;
function MyComponent () {
const [ session ] = useOnyx ( ONYXKEYS . SESSION );
if ( ! session ?. authToken ) {
return < LoginScreen />;
}
return < AppContent accountID ={ session . accountID } />;
}
Sign In
Basic Sign In
import * as Session from '@libs/actions/Session' ;
function signIn ( email : string , password : string ) {
Session . signIn ( email , password );
}
Sign In Implementation
From src/libs/actions/Session.ts:
function signIn ( email : string , password : string ) {
const optimisticData : OnyxUpdate [] = [
{
onyxMethod: Onyx . METHOD . MERGE ,
key: ONYXKEYS . ACCOUNT ,
value: {
isLoading: true ,
},
},
];
const successData : OnyxUpdate [] = [
{
onyxMethod: Onyx . METHOD . MERGE ,
key: ONYXKEYS . ACCOUNT ,
value: {
isLoading: false ,
},
},
];
const failureData : OnyxUpdate [] = [
{
onyxMethod: Onyx . METHOD . MERGE ,
key: ONYXKEYS . ACCOUNT ,
value: {
isLoading: false ,
errors: {
[Date. now ()]: 'Failed to sign in' ,
},
},
},
];
API . write (
'SignIn' ,
{ email , password },
{ optimisticData , successData , failureData },
);
}
Short-Lived Auth Token
For magic links and deep links:
function signInWithShortLivedAuthToken ( shortLivedAuthToken : string ) {
API . read (
'SignInWithShortLivedAuthToken' ,
{ authToken: shortLivedAuthToken },
);
}
Automatic Reauthentication
How It Works
When the server returns jsonCode: 407 (expired auth token):
Reauthentication middleware intercepts the response
Automatically calls Reauthenticate API
Gets new authToken
Retries the original request
User never sees an error
Reauthentication Middleware
From src/libs/Middleware/Reauthentication.ts:
function Reauthentication ( response : Response , request : Request ) : Promise < Response > {
if ( response ?. jsonCode !== 407 ) {
return Promise . resolve ( response );
}
// Get stored credentials
const credentials = getCredentials ();
if ( ! credentials ) {
// No credentials, redirect to login
redirectToSignIn ();
return Promise . reject ( new Error ( 'Unable to reauthenticate' ));
}
// Call Reauthenticate API
return API . write ( 'Reauthenticate' , credentials )
. then (() => {
// Retry original request
return retryRequest ( request );
});
}
Credentials Storage
Credentials are stored separately from session:
import ONYXKEYS from '@src/ONYXKEYS' ;
type Credentials = {
login : string ; // Email or phone
password : string ; // Encrypted password
autoGeneratedLogin ?: string ;
autoGeneratedPassword ?: string ;
};
// Stored at
ONYXKEYS . CREDENTIALS
Sign Out
Basic Sign Out
import * as Session from '@libs/actions/Session' ;
function handleSignOut () {
Session . signOut ();
}
Sign Out Implementation
function signOut () {
// Clear session first (optimistic)
Onyx . set ( ONYXKEYS . SESSION , null );
Onyx . set ( ONYXKEYS . CREDENTIALS , null );
// Notify server
API . write ( 'SignOut' , {});
// Navigate to login
Navigation . navigate ( ROUTES . HOME );
}
Two-Factor Authentication (2FA)
Enabling 2FA
function enable2FA () {
API . write ( 'Account_TwoFactorAuthGenerate' , {});
}
Validating 2FA Code
function validate2FACode ( twoFactorAuthCode : string ) {
API . write ( 'Account_TwoFactorAuthValidate' , { twoFactorAuthCode });
}
Sign In with 2FA
When 2FA is enabled, sign-in is a two-step process:
// Step 1: Initial sign in
function signInWith2FA ( email : string , password : string ) {
API . write ( 'SignIn' , { email , password });
// Server returns requiresTwoFactorAuth: true
}
// Step 2: Validate 2FA code
function validate2FASignIn ( twoFactorAuthCode : string ) {
API . write ( 'SignIn_Validate2FA' , { twoFactorAuthCode });
// Server returns authToken on success
}
Magic Code / OTP
Request Magic Code
function requestMagicCode ( email : string ) {
API . write ( 'RequestMagicCode' , { email });
// Code sent via email
}
Sign In with Magic Code
function signInWithMagicCode ( email : string , magicCode : string ) {
API . write ( 'SignInWithMagicCode' , { email , magicCode });
}
Auth Token Management
Including Auth Token in Requests
Auth token is automatically included in all requests:
// Handled automatically by API client
const headers = {
'Authorization' : `Bearer ${ session . authToken } ` ,
};
Token Expiration
Auth tokens expire after a period of inactivity:
Expiration : ~30 days of inactivity
Handling : Automatic reauthentication
User Impact : Seamless, no action required
Encrypted Auth Token
For sensitive operations (payments, bank accounts):
type Session = {
authToken : string ; // Regular token
encryptedAuthToken ?: string ; // For sensitive ops
};
// Automatically used for payment-related APIs
Account Validation
Email Validation
New accounts must validate their email:
function validateEmail ( validateCode : string ) {
API . write ( 'ValidateEmail' , { validateCode });
}
Adding New Login Method
function addNewContactMethod ( email : string ) {
API . write ( 'AddNewContactMethod' , { partnerUserID: email });
// Validation email sent
}
function validateNewContactMethod ( validateCode : string ) {
API . write ( 'ValidateContactMethod' , { validateCode });
}
Handling Deleted Accounts
When an account is deleted (jsonCode 408):
// Middleware handles this automatically
function handleDeletedAccount ( response : Response ) : Promise < Response > {
if ( response ?. jsonCode !== 408 ) {
return Promise . resolve ( response );
}
// Clear session
Onyx . set ( ONYXKEYS . SESSION , null );
// Show message
showAlertMessage ( 'Account has been deleted' );
// Redirect to login
Navigation . navigate ( ROUTES . HOME );
return Promise . reject ( new Error ( 'Account deleted' ));
}
Testing Authentication
Mock Session
import Onyx from 'react-native-onyx' ;
import ONYXKEYS from '@src/ONYXKEYS' ;
beforeEach ( async () => {
await Onyx . merge ( ONYXKEYS . SESSION , {
authToken: 'test-auth-token' ,
accountID: 1 ,
email: 'test@example.com' ,
});
});
test ( 'requires authentication' , () => {
const { getByText } = render (< ProtectedComponent />);
expect ( getByText ( 'Protected Content' )). toBeTruthy ();
});
Mock Sign In
import * as Session from '@libs/actions/Session' ;
jest . spyOn ( Session , 'signIn' ). mockImplementation (() => {
Onyx . merge ( ONYXKEYS . SESSION , {
authToken: 'mock-token' ,
accountID: 1 ,
email: 'test@example.com' ,
});
});
Authentication Best Practices
1. Check Auth State
// ✅ Good: Check auth before protected actions
function MyComponent () {
const [ session ] = useOnyx ( ONYXKEYS . SESSION );
if ( ! session ?. authToken ) {
return < SignInPrompt />;
}
return < ProtectedContent />;
}
2. Handle Unauthenticated Users
import interceptAnonymousUser from '@libs/interceptAnonymousUser' ;
function handleProtectedAction () {
interceptAnonymousUser (() => {
// This only runs if user is authenticated
performProtectedAction ();
});
// Redirects to login if not authenticated
}
3. Never Store Passwords
// ❌ Bad: Storing plain text password
Onyx . merge ( ONYXKEYS . CREDENTIALS , {
password: 'user-password' , // Don't do this
});
// ✅ Good: Let Session actions handle credentials
Session . signIn ( email , password );
// Password is encrypted before storage
4. Trust Automatic Reauthentication
// ❌ Bad: Manual token refresh
if ( isTokenExpired ( authToken )) {
refreshToken ();
}
// ✅ Good: Let middleware handle it
API . write ( 'SomeCommand' , params );
// Middleware automatically reauthenticates if needed
Security Considerations
Secure Storage
Auth tokens stored in secure storage on native platforms
Web uses httpOnly cookies when possible
Credentials are encrypted before storage
HTTPS Only
All API calls use HTTPS:
const EXPENSIFY_URL = 'https://www.expensify.com' ;
const SECURE_EXPENSIFY_URL = 'https://www.expensify.com/api' ;
Token Rotation
Tokens are rotated on:
Sign in
Reauthentication
Password change
Security events
Troubleshooting
Stuck at Login
// Check session state
Onyx . connect ({
key: ONYXKEYS . SESSION ,
callback : ( session ) => {
console . log ( 'Session:' , session );
},
});
Reauthentication Loop
// Check credentials
Onyx . connect ({
key: ONYXKEYS . CREDENTIALS ,
callback : ( credentials ) => {
console . log ( 'Credentials:' , credentials );
// If null, user needs to sign in again
},
});
407 Errors
Check that credentials are stored
Verify password hasn’t changed
Clear app data and sign in again
Next Steps
API Overview Learn API fundamentals
API Endpoints Explore available endpoints
State Management Understand session in Onyx
Testing Test authenticated flows