Overview
DPM Delivery Mobile implements a secure authentication system using phone number and password credentials. The app stores authentication tokens using platform-specific secure storage solutions, with a web crypto fallback for browser environments.
Login Flow
The login form is built with React Hook Form and Zod validation, providing a clean and type-safe user experience.
src/modules/auth/login-form.tsx
import { FormField } from "@/components/form-field" ;
import { useErrorHandler } from "@/hooks/use-error-handler" ;
import { api } from "@/services/api" ;
import { Storage , StorageKeys } from "@/utils/storage" ;
import { zodResolver } from "@hookform/resolvers/zod" ;
import { useMutation } from "@tanstack/react-query" ;
import { useRouter } from "expo-router" ;
import { Button , Spinner } from "heroui-native" ;
import { useForm } from "react-hook-form" ;
import { loginSchema , type LoginSchemaInput } from "./validations" ;
export function LoginForm () {
const form = useForm < LoginSchemaInput >({
resolver: zodResolver ( loginSchema ),
});
const { handleError } = useErrorHandler ( "LoginForm" );
const router = useRouter ();
const loginMutation = useMutation ({
mutationFn: api . auth . login ,
retry: 1 ,
});
const onSubmit = async ( data : LoginSchemaInput ) => {
try {
const response = await loginMutation . mutateAsync ( data );
Storage . setToken ( StorageKeys . AUTH_TOKEN , response . data . accessToken );
Storage . setObject ( StorageKeys . USER , response . data . user );
router . replace ( "/" );
} catch ( error ) {
handleError ( error );
}
};
return (
< View >
< FormField
control = { form . control }
name = "phone"
label = "Phone Number"
placeholder = "Enter your phone number"
inputMode = "tel"
keyboardType = "phone-pad"
/>
< FormField
control = { form . control }
name = "password"
label = "Password"
placeholder = "Enter your password"
secureTextEntry
/>
< Button
onPress = { form . handleSubmit ( onSubmit ) }
isDisabled = { loginMutation . isPending }
>
{ loginMutation . isPending ? < Spinner /> : null }
Log In
</ Button >
</ View >
);
}
Key Features
Phone Authentication Users log in with their phone number and password for quick access.
Secure Storage Tokens are stored using SecureStore on native platforms and encrypted localStorage on web.
Error Handling Comprehensive error handling with user-friendly error messages.
Type Safety Full TypeScript support with Zod schema validation.
Secure Token Storage
Storage Implementation
The app uses a unified storage API that automatically selects the appropriate storage mechanism based on the platform.
import * as SecureStore from "expo-secure-store" ;
import { Platform } from "react-native" ;
import { createMMKV } from "react-native-mmkv" ;
import { encryptForWeb , decryptForWeb } from "./web-crypto" ;
export enum StorageKeys {
AUTH_TOKEN = "auth_token" ,
REFRESH_TOKEN = "refresh_token" ,
USER = "user" ,
DEVICE_KEY = "__device_key" ,
WEB_CRYPTO_KEY = "__web_crypto_key" ,
}
// Secure token storage with platform-specific implementations
async function setToken ( key : string , value : string ) : Promise < void > {
if ( Platform . OS === "web" ) {
const encrypted = await encryptForWeb ( value );
localStorage . setItem ( key , encrypted );
} else {
await SecureStore . setItemAsync ( key , value );
}
}
async function getToken ( key : string ) : Promise < string | null > {
if ( Platform . OS === "web" ) {
const encrypted = localStorage . getItem ( key );
if ( ! encrypted ) return null ;
return await decryptForWeb ( encrypted );
} else {
return await SecureStore . getItemAsync ( key );
}
}
export const Storage = {
setToken ,
getToken ,
deleteToken ,
// ... other methods
};
On native platforms, the app uses:
SecureStore for sensitive data (auth tokens)
MMKV for encrypted general storage
Device-specific encryption keys
On web platforms, the app uses:
Web Crypto API for encryption
localStorage for encrypted token storage
AES-GCM 256-bit encryption
Web Crypto Fallback
AES-GCM Encryption
For web environments, the app implements AES-GCM encryption to secure authentication tokens in localStorage.
const IV_LENGTH = 12 ;
const KEY_LENGTH = 256 ;
// Generate or retrieve a device-specific encryption key
async function getWebEncryptionKey () : Promise < CryptoKey > {
const storedKeyData = localStorage . getItem ( WEB_CRYPTO_KEY );
if ( storedKeyData ) {
const keyData = JSON . parse ( storedKeyData );
return await crypto . subtle . importKey (
"jwk" ,
keyData ,
{ name: "AES-GCM" , length: KEY_LENGTH },
true ,
[ "encrypt" , "decrypt" ]
);
}
const key = await crypto . subtle . generateKey (
{ name: "AES-GCM" , length: KEY_LENGTH },
true ,
[ "encrypt" , "decrypt" ]
);
const exportedKey = await crypto . subtle . exportKey ( "jwk" , key );
localStorage . setItem ( WEB_CRYPTO_KEY , JSON . stringify ( exportedKey ));
return key ;
}
// Encrypt a string value using AES-GCM
export async function encryptForWeb ( plaintext : string ) : Promise < string > {
const key = await getWebEncryptionKey ();
const iv = crypto . getRandomValues ( new Uint8Array ( IV_LENGTH ));
const encoder = new TextEncoder ();
const data = encoder . encode ( plaintext );
const encryptedData = await crypto . subtle . encrypt (
{ name: "AES-GCM" , iv: iv },
key ,
data
);
// Combine IV + encrypted data and convert to base64
const combined = new Uint8Array ( iv . length + encryptedData . byteLength );
combined . set ( iv , 0 );
combined . set ( new Uint8Array ( encryptedData ), iv . length );
return btoa ( String . fromCharCode ( ... combined ));
}
Security Features
256-bit AES-GCM Encryption
Uses industry-standard AES-GCM encryption with 256-bit keys for maximum security.
Random Initialization Vectors
Each encryption operation uses a unique randomly generated IV for enhanced security.
Encryption keys are generated per device and stored securely in the browser.
Encrypted data is base64-encoded for safe storage in localStorage.
Authentication Service
The authentication service provides a clean API for login operations.
src/services/auth/auth.service.ts
import { apiEndPoints } from "../api/end-points" ;
import type { HttpClient } from "../http.service" ;
import type { AuthService } from "./interface" ;
export function createAuthService ( httpClient : HttpClient ) : AuthService {
return {
login : ( data ) => httpClient . post ( apiEndPoints . auth . login (), data ),
};
}
Best Practices
Validate Input
Always validate phone numbers and passwords using Zod schemas before submission.
Handle Errors Gracefully
Use the error handler hook to display user-friendly error messages.
Secure Token Storage
Never store tokens in plain text. Always use the Storage utility functions.
Clear Tokens on Logout
Remove all tokens and user data when users log out.
The authentication system is designed to work seamlessly across iOS, Android, and web platforms with appropriate security measures for each environment.
Profile Management Learn how users manage their profile and logout.
API Services Understand how API calls are authenticated.