Skip to main content
DOSS uses JWT-based authentication stored in device-local AsyncStorage. On every app launch, the app reads the stored token to determine whether to show the authenticated or unauthenticated experience — no network call is required for the initial check.

Two entry paths

Registration

New users verify their email, verify their phone number, and set a 4-digit PIN to create an account.

Login

Returning users enter their phone number and 4-digit PIN to authenticate.

Auth state

Auth state is managed globally by a Zustand store defined in src/store/StateManager/useAuthStore.js.
useAuthStore.js
import {create} from 'zustand';

export const useAuthStore = create(set => ({
  loggedIn: false,
  setLoggedIn: loggedIn => set({loggedIn}),
  user: {},
  setUser: userInfo => set({user: userInfo}),
}));
FieldTypeDescription
loggedInbooleanWhether the current session is authenticated. Defaults to false.
userobjectThe authenticated user object, populated after login or on profile fetch.
setLoggedInfunctionSets the loggedIn flag. Called after a successful login, registration, or token check.
setUserfunctionStores the user profile object in state.

App boot sequence

The Root component (src/navigation/Root/Root.jsx) runs an async token check on mount:
Root.jsx
useEffect(() => {
  const init = async () => {
    try {
      const token = await storage.getData('token');
      setLoggedIn(!!token);
    } catch (e) {
      setLoggedIn(false);
    }
  };
  init();
}, [setLoggedIn]);
While the token is being read, a <Loader /> is displayed. Once resolved, navigation proceeds. All navigation is managed by a MainStack component that conditionally renders one of two stacks based on loggedIn:

UnAuthStack screens

All screens in the unauthenticated stack start from AUTH_INTRO:
Route constantScreenPurpose
AUTH_INTROAuthIntroEntry point — choice between Login and Start KYC
LOGINLoginInputPhone number input for login
ENTER_PINEnterPinPIN entry to complete login
EMAIL_INPUTEmailInputEmail address input for registration
EMAIL_VERIFYEmailVerifyEmail OTP verification
EMAIL_VERIFY_SUCCESSEmailVerifySuccessEmail verified confirmation
NUMBER_INPUTNumberInputPhone number input for registration
NUMBER_VERIFYNumberVerifyPhone OTP verification
NUMBER_VERIFY_SUCCESSNumberVerifySuccessPhone verified confirmation
EMAIL_NUMBER_VERIFY_SUCCESSEmailNumberVerifySuccessBoth verified — proceed to PIN creation
CREATE_PIN_INPUTCreatePinInputSet new 4-digit PIN
CONFIRM_PINConfirmPinConfirm the new PIN
CREATE_PIN_SUCCESSCreatePinSuccessPIN created — sets loggedIn: true
SIGN_UP_SUCCESSSignUpSuccessAccount creation complete
Once authenticated, PIN-related screens are accessible from the AuthStack:
Route constantScreenPurpose
RESET_PIN_INPUTResetPinInputEnter a new PIN during reset
RESET_CONFIRM_PINResetConfirmPinConfirm the new PIN during reset
RESET_PIN_SUCCESSResetPinSuccessPIN reset complete
PAY_ENTER_PINPayEnterPinPIN entry to approve/reject a payment request
ENTER_GROUP_PINEnterGroupPin6-digit code entry to join a group

Security model

The JWT token is stored in AsyncStorage under the key token. It is set immediately after a successful login (EnterPin) or after PIN confirmation during registration (ConfirmPin).
Token expiry is not handled with an automatic refresh flow. If an API call fails due to an expired token, the user must log in again. Logging out clears the token from AsyncStorage and sets loggedIn to false.
  • PIN requirement: All payment actions require the user to re-enter their 4-digit PIN at the point of action. The PIN is never cached in memory after login.
  • FCM token: A Firebase Cloud Messaging device token (fcm_token) is retrieved from AsyncStorage and sent with every login and PIN confirmation request to enable push notifications on the authenticated device.
  • Deep links: The app handles doss:// deep links. Notifications that open the app navigate to doss://Notifications via Linking.openURL.

Build docs developers (and LLMs) love