The Authentication module provides a complete user authentication flow including login, signup, password management, and third party login. It uses the CDF (Central Data Framework) for handling authentication operations.
Authentication is implemented using ESP RainMaker's user management module with support for email based authentication and also integration with third party social logins like Gmail and Apple.
- To know more about enabling third party login, click here.
Authentication
This guide explains how the Authentication module works, focusing on the major operations and how CDF (Central Data Framework) APIs are used throughout the system.
User Flow Overview
The Authentication module has five main user flows:
- New User Registration: Login → Signup → Code Verification → Auto Login → Home
- Existing User Login: Login → Home
- Third Party Login: Login → Third Party Provider → Success → Home
- Password Reset: Login → Forgot Password → Reset Password (OTP + New Password) → Login
- Change Password: Home → Profile → Account & Security → Change Password → Success → Auto Logout → Login
Architecture Overview
Flow Explanation:
- Entry Point: User starts at Login.tsx
- Authentication Module: Login, Signup, or Third Party options
- Third Party Flow: User clicks provider icon → Redirects to provider → Returns on success
- Signup Verification: Signup → Code verification (ConfirmationCode.tsx) → Auto login → Home
- Password Reset Flow: Forgot Password → Reset Password (OTP + New Password in single screen) → Login
- Success: Redirect to home screen or back to login
- Change Password: From home screen → Profile → Account & Security → Change password → Auto logout → Back to login
Components Structure
app/(auth)/
├── Login.tsx # Main login screen
├── Signup.tsx # User registration
├── ConfirmationCode.tsx # Code verification (for signup)
├── Forgot.tsx # Password reset request
├── ResetPassword.tsx # Password reset with OTP and new password
└── ChangePassword.tsx # Password change
Terms of Use and Privacy Policy are shown in the signup consent section. The URLs are configured in utils/constants.ts via TERMS_OF_USE_LINK and PRIVACY_POLICY_LINK. The consent checkbox, link text, and handlers that open these URLs in a browser are implemented in app/(auth)/Signup.tsx (see showTerms and showPrivacy).
1. Login Screen (Login.tsx)
The main authentication module screen where users can sign in.
CDF Store
// Get CDF stores
const { store } = useCDF();
const { userStore } = store;
// Access auth instance
const authInstance = store.userStore.authInstance;
Login Operations
// Get CDF stores and hooks
const { store, fetchNodesAndGroups, initUserCustomData, refreshESPRMUser } = useCDF();
const { t } = useTranslation();
const router = useRouter();
const toast = useToast();
// Email/Password login
const login = async () => {
// Check if form is valid before submitting
if (!isEmailValid || !isPasswordValid) {
return;
}
setIsLoading(true);
try {
const res = await store.userStore.login(email, password);
if (!res) {
return;
}
await executePostLoginPipeline({
store,
router,
refreshESPRMUser,
fetchNodesAndGroups,
initUserCustomData,
});
} catch (error: any) {
toast.showError(
t("auth.errors.signInFailed"),
error?.description || t("auth.errors.fallback")
);
} finally {
// reset loading state
setIsLoading(false);
}
};
2. Third Party Login Flow
The authentication module handles login through external providers like Google and Apple.
User Flow Steps
// Get OAuth providers configuration
const ENABLED_OAUTH_PROVIDERS = Constants.expoConfig?.extra?.enabledThirdPartyProviders || [];
const OAUTH_PROVIDER_IMAGES = {
google: google,
signinwithapple: signinwithapple,
} as Record<string, ImageSourcePropType>;
// 1. User clicks third party provider icon
const oauthLogin = async (provider: string) => {
setIsOAuthLoading(true);
setPipelineProgress(null);
try {
const authInstance = store.userStore.authInstance;
if (!authInstance) {
throw new Error("Auth instance not found");
}
// 2. Redirect to provider's authentication page
const userInstance = await authInstance.loginWithOauth(provider);
store.userStore[CDF_EXTERNAL_PROPERTIES.IS_OAUTH_LOGIN] = true;
await store.userStore.setUserInstance(userInstance);
// 3. On success, execute post-login pipeline
if (userInstance) {
// Initialize pipeline progress tracking
const pipelineSteps = [
{ name: "refreshESPRMUser", status: "pending" as const },
{ name: "setUserTimeZone", status: "pending" as const },
{ name: "createPlatformEndpoint", status: "pending" as const },
{ name: "fetchNodesAndGroups", status: "pending" as const },
{ name: "updateRefreshTokensForAllAIDevices", status: "pending" as const },
{ name: "initUserCustomData", status: "pending" as const },
{ name: "getUserProfileAndRoute", status: "pending" as const },
];
setPipelineProgress({
currentStep: "",
completed: 0,
total: pipelineSteps.length,
steps: pipelineSteps,
});
await executePostLoginPipeline({
store,
router,
refreshESPRMUser,
fetchNodesAndGroups,
initUserCustomData,
onStepStart: (stepName) => {
setPipelineProgress((prev) => {
if (!prev) return prev;
return {
...prev,
currentStep: stepName,
steps: prev.steps.map((step) =>
step.name === stepName ? { ...step, status: "running" as const } : step
),
};
});
},
onStepComplete: (stepName) => {
setPipelineProgress((prev) => {
if (!prev) return prev;
const updatedSteps = prev.steps.map((step) =>
step.name === stepName ? { ...step, status: "completed" as const } : step
);
const completed = updatedSteps.filter((s) => s.status === "completed").length;
return {
...prev,
currentStep: stepName,
completed,
steps: updatedSteps,
};
});
},
onProgress: (stepName, state) => {
setPipelineProgress((prev) => {
if (!prev) return prev;
return {
...prev,
currentStep: stepName,
completed: state.completed,
total: state.total,
};
});
},
});
}
} catch (error) {
console.error(`OAuth login failed for provider ${provider}:`, error);
// Handle different types of OAuth errors
let errorMessage = "OAuth login failed. Please try again.";
if (error instanceof Error) {
if (error.message.includes("OAUTH_CANCELLED")) {
errorMessage = "OAuth login was cancelled.";
} else if (error.message.includes("NO_BROWSER_FOUND")) {
errorMessage = "No browser app found. Please install a browser.";
} else {
errorMessage = `OAuth error: ${error.message}`;
}
}
// Show error toast to user
toast.showError("OAuth Login Failed", errorMessage);
setPipelineProgress(null);
} finally {
// Hide loader when login completes (success or failure)
setIsOAuthLoading(false);
}
};
Provider Icons Implementation
// Configuration for enabled OAuth providers
const ENABLED_OAUTH_PROVIDERS = Constants.expoConfig?.extra?.enabledThirdPartyProviders || [];
const OAUTH_PROVIDER_IMAGES = {
google: google,
signinwithapple: signinwithapple,
} as Record<string, ImageSourcePropType>;
// Third party provider buttons
{ENABLED_OAUTH_PROVIDERS.length > 0 && (
<>
<Text {...testProps("text_3plogin")} style={globalStyles.thirdLoginText}>
{t("auth.login.thirdPartyLogin")}
</Text>
<View {...testProps("view_3plogin")} style={globalStyles.oauthContainer}>
{/* Enabled OAuth Providers */}
{ENABLED_OAUTH_PROVIDERS.map((provider) => (
<TouchableOpacity
key={provider}
onPress={() => oauthLogin(provider)}
style={globalStyles.oauthButton}
{...testProps(`button_3p_${provider}`)}
>
<Image
{...testProps(`image_3p_${provider}`)}
source={OAUTH_PROVIDER_IMAGES[provider.toLocaleLowerCase()]}
style={globalStyles.oauthImage}
/>
</TouchableOpacity>
))}
</View>
</>
)}
What this does:
- Checks if OAuth providers are enabled via
Constants.expoConfig?.extra?.enabledThirdPartyProviders - User clicks on third party provider icon (Google, Apple, etc.)
- App redirects to the provider's authentication page via
authInstance.loginWithOauth() - Provider handles authentication and returns user instance
- Sets
CDF_EXTERNAL_PROPERTIES.IS_OAUTH_LOGINflag to true - User instance is stored in CDF via
store.userStore.setUserInstance() - On success, executes post-login pipeline with progress tracking
- Shows loading screen with progress indicators during pipeline execution
- Tracks pipeline steps (refreshESPRMUser, setUserTimeZone, createPlatformEndpoint, etc.)
- Displays friendly step names to user during pipeline execution
- Handles cancellation, browser-related errors, and other OAuth errors
- Shows appropriate error messages via toast notifications
- Provides cancel button to abort OAuth login process
3. Signup Screen (Signup.tsx)
Handles new user registration with email verification.
// Get CDF stores and hooks
const { store } = useCDF();
const { t } = useTranslation();
const router = useRouter();
const toast = useToast();
// Form state
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [isEmailValid, setIsEmailValid] = useState(false);
const [isPasswordValid, setIsPasswordValid] = useState(false);
const [isConfirmPasswordValid, setIsConfirmPasswordValid] = useState(false);
const [consentChecked, setConsentChecked] = useState(false);
const [isLoading, setIsLoading] = useState(false);
// Email validator
const emailValidator = (email: string): { isValid: boolean; error?: string } => {
if (!email.trim()) {
return { isValid: false };
}
if (!validateEmail(email)) {
return { isValid: false, error: t("auth.validation.invalidEmail") };
}
return { isValid: true };
};
// Password validator
const passwordValidator = (password: string): { isValid: boolean; error?: string } => {
if (!password.trim()) {
return { isValid: false };
}
return { isValid: true };
};
// Confirm password validator
const confirmPasswordValidator = (confirmPwd: string): { isValid: boolean; error?: string } => {
if (!confirmPwd.trim()) {
return { isValid: false };
}
if (confirmPwd !== password) {
return {
isValid: false,
error: t("auth.validation.passwordsDoNotMatch"),
};
}
return { isValid: true };
};
// Send signup verification code
const signup = () => {
// Check if all fields are valid before submitting
if (
!isEmailValid ||
!isPasswordValid ||
!isConfirmPasswordValid ||
!consentChecked
) {
return;
}
setIsLoading(true);
// send sign up code
store.userStore.authInstance
?.sendSignUpCode(email, password)
.then((res) => {
if (res.status === "success") {
// redirect to code screen
router.push({
pathname: "/(auth)/ConfirmationCode",
params: {
email: email,
password: password,
},
});
} else {
toast.showError(
t("auth.errors.verificationCodeSendFailed"),
res.description
);
}
})
.catch((error) => {
toast.showError(
t("auth.errors.verificationCodeSendFailed"),
error.description || t("auth.errors.fallback")
);
})
.finally(() => {
// reset loading state
setIsLoading(false);
});
};
What this does:
- Validates user input (email, password, confirm password)
- Requires consent checkbox to be checked (Terms of Use and Privacy Policy)
- Sends verification code via CDF auth instance
- Navigates to code verification screen with email and password params
- Handles errors and loading states
4. Confirmation Code Screen (ConfirmationCode.tsx)
Handles verification codes for new user signup only. Password reset uses ResetPassword.tsx instead.
Code Verification
// Get CDF stores and params
const { store, initUserCustomData, refreshESPRMUser, fetchNodesAndGroups } = useCDF();
const { email, password = "" } = useLocalSearchParams();
const toast = useToast();
const [code, setCode] = useState("");
const [isCodeValid, setIsCodeValid] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [countdown, setCountdown] = useState(0);
// Code validator
const codeValidator = (inputCode: string): { isValid: boolean; error?: string } => {
if (!inputCode.trim()) {
return { isValid: false };
}
if (inputCode.trim().length !== 6) {
return {
isValid: false,
error: t("auth.validation.invalidCode"),
};
}
return { isValid: true };
};
// Handle code verification for signup
const handleVerify = () => {
// Check if the code is valid before submitting
if (!isCodeValid || !code.trim()) {
return;
}
setIsLoading(true);
// Confirm signup
store.userStore.authInstance
?.confirmSignUp(email as string, code)
.then(async (res) => {
if (res.status === "success") {
toast.showSuccess(t("auth.signup.registrationSuccess"));
// Auto-login after successful signup verification
await loginUser();
} else {
toast.showError(
t("auth.errors.signupConfirmationFailed"),
res.description || t("auth.errors.fallback")
);
}
})
.catch((error) => {
toast.showError(
t("auth.errors.signupConfirmationFailed"),
error.description || t("auth.errors.fallback")
);
})
.finally(() => {
setIsLoading(false);
});
};
// Auto-authenticates the user after signup verification
const loginUser = async () => {
try {
const res = await store.userStore.login(email as string, password as string);
if (res) {
router.dismissAll();
await executePostLoginPipeline({
store,
router,
refreshESPRMUser,
fetchNodesAndGroups,
initUserCustomData,
});
}
} catch (error: any) {
toast.showError(
t("auth.errors.autoSignInFailed"),
error.description || t("auth.errors.fallback")
);
setTimeout(() => {
router.dismissTo({
pathname: "/(auth)/Login",
params: { email: email },
});
}, 10000);
}
};
Resend Code Functionality
// Resend verification code for signup
const handleResendCode = () => {
if (countdown > 0) return;
setIsLoading(true);
// Send verification code for signup
store.userStore.authInstance
?.sendSignUpCode(email as string, password as string)
.then((res) => {
if (res.status === "success") {
toast.showSuccess(t("auth.verification.heading"));
// reset countdown to 60 seconds
setCountdown(60);
} else {
toast.showError(
t("auth.errors.verificationCodeSendFailed"),
res.description || t("auth.errors.fallback")
);
}
})
.catch((error) => {
toast.showError(
t("auth.errors.verificationCodeSendFailed"),
error.description || t("auth.errors.fallback")
);
})
.finally(() => {
// reset loading state
setIsLoading(false);
});
};
What this does:
- Handles signup verification code entry (6-digit code)
- Uses CDF auth instance
confirmSignUpmethod for verification - Provides resend functionality with 60-second countdown timer
- Auto-login after successful signup verification using
executePostLoginPipeline - Redirects to home screen after successful verification via post-login pipeline
- Handles auto-login failures with error toast and redirects to login screen after 10 seconds
5. Forgot Password Screen (Forgot.tsx)
Handles password reset request for existing users. This is the initial step where users enter their email to receive a verification code.
// Get CDF stores and hooks
const { t } = useTranslation();
const { store } = useCDF();
const router = useRouter();
const toast = useToast();
// Form state
const [email, setEmail] = useState("");
const [isEmailValid, setIsEmailValid] = useState(false);
const [isLoading, setIsLoading] = useState(false);
// Email validator
const emailValidator = (email: string): { isValid: boolean; error?: string } => {
if (!email.trim()) {
return { isValid: false };
}
if (!validateEmail(email)) {
return { isValid: false, error: t("auth.validation.invalidEmail") };
}
return { isValid: true };
};
// Handle email change
const handleEmailChange = (value: string, isValid: boolean) => {
setEmail(value.trim());
setIsEmailValid(isValid);
};
// Sends verification code to user's email for password reset
const sendVerificationCode = () => {
// Check if email is valid before submitting
if (!isEmailValid || !email) {
return;
}
setIsLoading(true);
// send verification code to user's email address
store.userStore.authInstance
?.forgotPassword(email)
.then((res) => {
// if success, redirect to reset password screen
if (res.status === "success") {
toast.showSuccess(t("auth.verification.heading"));
router.push({
pathname: "/(auth)/ResetPassword",
params: {
email: email,
},
});
} else {
toast.showError(
t("auth.errors.verificationCodeSendFailed"),
res.description || t("auth.errors.fallback")
);
}
})
.catch((error) => {
toast.showError(
t("auth.errors.verificationCodeSendFailed"),
error.description || t("auth.errors.fallback")
);
})
.finally(() => {
setIsLoading(false);
});
};
What this does:
- Collects user email address with validation
- Sends password reset verification code via CDF auth instance
forgotPasswordmethod - Shows success toast when code is sent
- Navigates to ResetPassword.tsx screen with email param
- Handles validation and error states
6. Reset Password Screen (ResetPassword.tsx)
Handles password reset with OTP verification. This screen combines OTP verification and new password entry into a single step.
// Get CDF stores and params
const { t } = useTranslation();
const { store } = useCDF();
const { email } = useLocalSearchParams();
const toast = useToast();
// Form state
const [code, setCode] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [isCodeValid, setIsCodeValid] = useState(false);
const [isPasswordValid, setIsPasswordValid] = useState(false);
const [isConfirmPasswordValid, setIsConfirmPasswordValid] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [countdown, setCountdown] = useState(0);
// Code validator - checks if code is 6 digits
const codeValidator = (inputCode: string): { isValid: boolean; error?: string } => {
if (!inputCode.trim()) {
return { isValid: false };
}
if (inputCode.trim().length !== 6) {
return {
isValid: false,
error: t("auth.validation.invalidCode"),
};
}
return { isValid: true };
};
// Password validator
const passwordValidator = (password: string): { isValid: boolean; error?: string } => {
if (!password.trim()) {
return { isValid: false };
}
return { isValid: true };
};
// Confirm password validator - checks if passwords match
const confirmPasswordValidator = useCallback(
(confirmPwd: string): { isValid: boolean; error?: string } => {
if (!confirmPwd.trim()) {
return { isValid: false };
}
if (confirmPwd !== newPassword) {
return {
isValid: false,
error: t("auth.validation.passwordsDoNotMatch"),
};
}
return { isValid: true };
},
[newPassword, t]
);
// Handle password change - re-validates confirm password when new password changes
const handlePasswordChange = (value: string, isValid: boolean) => {
const newPwd = value.trim();
setNewPassword(newPwd);
setIsPasswordValid(isValid);
// Re-validate confirm password when new password changes
if (confirmPassword.trim()) {
const isConfirmValid = confirmPassword.trim() === newPwd;
setIsConfirmPasswordValid(isConfirmValid);
}
};
// Handle password reset with OTP verification
const handleResetPassword = () => {
// Check if all fields are valid before submitting
if (
!isCodeValid ||
!code.trim() ||
!isPasswordValid ||
!newPassword.trim() ||
!isConfirmPasswordValid ||
!confirmPassword.trim()
) {
return;
}
setIsLoading(true);
store.userStore.authInstance
?.setNewPassword(email as string, newPassword, code)
.then((res) => {
if (res.status === "success") {
toast.showSuccess(t("auth.forgotPassword.resetSuccess"));
// redirect to login page
router.dismissTo({
pathname: "/(auth)/Login",
params: { email: email },
});
} else {
toast.showError(
t("auth.errors.passwordResetFailed"),
res.description || t("auth.errors.fallback")
);
}
})
.catch((error) => {
toast.showError(
t("auth.errors.passwordResetFailed"),
error.description || t("auth.errors.fallback")
);
})
.finally(() => {
setIsLoading(false);
});
};
// Resend verification code
const handleResendCode = () => {
if (countdown > 0) return;
setIsLoading(true);
// Send verification code for password reset
store.userStore.authInstance
?.forgotPassword(email as string)
.then((res) => {
if (res.status === "success") {
toast.showSuccess(t("auth.verification.heading"));
// reset countdown to 60 seconds
setCountdown(60);
} else {
toast.showError(
t("auth.errors.verificationCodeSendFailed"),
res.description || t("auth.errors.fallback")
);
}
})
.catch((error) => {
toast.showError(
t("auth.errors.verificationCodeSendFailed"),
error.description || t("auth.errors.fallback")
);
})
.finally(() => {
// reset loading state
setIsLoading(false);
});
};
What this does:
- Displays 6-digit OTP code input field (auto-focused, numeric keyboard)
- Collects new password and confirm password with real-time validation
- Validates password matching in real-time using
confirmPasswordValidator - Re-validates confirm password when new password changes
- Verifies OTP code and sets new password via CDF auth instance
setNewPasswordmethod - Provides resend code functionality with 60-second countdown timer
- Shows success toast and redirects to login screen after successful password reset
- Passes email param to login screen for pre-filling
- Handles validation and error states