Skip to main content

Step 6: Build onboarding and authentication

What we are building​

An email-and-password patient flow with registration, sign-in, and a protected patient route group. The native forms call the configured Ovok client and activate the returned login; the app decides what to show before and after that flow.

What you should already have​

  • The configured sandbox client and tenant code from step 2.
  • The providers from step 5.
  • @ovok/native authentication forms available below OvokProvider and the native ThemeProvider.

The implementation​

Add the sign-in screen​

Create app/sign-in.tsx:

import { SignIn, useSession } from "@ovok/native";
import { Redirect, useRouter } from "expo-router";
import { useTranslation } from "react-i18next";
import { ActivityIndicator, Alert, View } from "react-native";

import { ovokConfig } from "../src/config/ovok";

export default function SignInScreen() {
const router = useRouter();
const { t } = useTranslation();
const { status } = useSession({ sessions: false });

if (status === "loading") return <ActivityIndicator />;
if (status === "authenticated") return <Redirect href="/(patient)" />;

return (
<View>
<SignIn>
<SignIn.Header>
<SignIn.Header.Title />
<SignIn.Header.Description />
</SignIn.Header>
<SignIn.EmailForm
loginType="Patient"
tenantCode={ovokConfig.tenantCode}
onSuccess={() => router.replace("/(patient)")}
onError={() =>
Alert.alert(t("auth.signInFailed"), t("errors.tryAgain"))
}
>
<SignIn.EmailForm.Inputs />
<SignIn.EmailForm.SigninButton />
</SignIn.EmailForm>
<SignIn.RegisterLink onPress={() => router.push("/register")} />
</SignIn>
</View>
);
}

SignIn.EmailForm owns the standard credential and supported MFA exchange. Its onSuccess callback runs after the form activates the login. Do not store a second copy of the credentials or build a parallel authentication request. See Native authentication and session management.

Add registration​

Create app/register.tsx:

import { Register, useSession } from "@ovok/native";
import { Redirect, useRouter } from "expo-router";
import { useTranslation } from "react-i18next";
import { ActivityIndicator, Alert, View } from "react-native";
import { Text } from "react-native-paper";

import { ovokConfig } from "../src/config/ovok";

export default function RegisterScreen() {
const router = useRouter();
const { t } = useTranslation();
const { status } = useSession({ sessions: false });

if (status === "loading") return <ActivityIndicator />;
if (status === "authenticated") return <Redirect href="/(patient)" />;

return (
<View>
<Register>
<Register.Header>
<Register.Header.Title />
<Register.Header.Description />
</Register.Header>
<Register.EmailForm
tenantCode={ovokConfig.tenantCode}
onSuccess={() => router.replace("/(patient)")}
onError={() =>
Alert.alert(t("auth.registrationFailed"), t("errors.tryAgain"))
}
onNextStep={(step) => {
if (step.nextStep === "clinician-invite") {
Alert.alert(
t("auth.checkEmailTitle"),
t("auth.checkEmailForInvitation"),
);
}
}}
>
<Register.EmailForm.Inputs />
<Register.EmailForm.RegisterButton />
</Register.EmailForm>
<Register.LoginLink onPress={() => router.push("/sign-in")} />
</Register>
</View>
);
}

The optional onNextStep handles the configured business-email clinician-invite outcome. If that project setting is not used, remove the callback. The ordinary patient-registration path creates and activates the patient account through the SDK; do not create a duplicate Patient resource.

Protect the patient route group​

Update app/(patient)/_layout.tsx so Bluetooth starts only after a patient is authenticated:

import { useSession } from "@ovok/native";
import { Redirect, Slot } from "expo-router";
import { ActivityIndicator } from "react-native";

import { BluetoothWorkspace } from "../../src/features/bluetooth/device-state";

export default function PatientLayout() {
const { status } = useSession({ sessions: false });

if (status === "loading") return <ActivityIndicator />;
if (status !== "authenticated") return <Redirect href="/sign-in" />;

return (
<BluetoothWorkspace>
<Slot />
</BluetoothWorkspace>
);
}

Create app/index.tsx to choose the initial route:

import { useSession } from "@ovok/native";
import { Redirect } from "expo-router";
import { ActivityIndicator } from "react-native";

export default function IndexScreen() {
const { status } = useSession({ sessions: false });

if (status === "loading") return <ActivityIndicator />;
return status === "authenticated" ? (
<Redirect href="/(patient)" />
) : (
<Redirect href="/sign-in" />
);
}

Expo Router still registers these screens as routes, but signed-in users are immediately redirected away from sign-in and registration. The protected layout also redirects a signed-out user before mounting the Bluetooth provider.

Insert your product's approved consent and privacy step before registration or before entering the monitoring workflow, according to the intended purpose and the decisions made by your clinical and legal teams. The SDK does not supply consent language or decide when consent is legally required. Do not treat a generic checkbox as a substitute for your approved consent flow.

Important Ovok decisions​

  • Patient self-registration needs PATIENT_REGISTRATION_ENABLED enabled and a project default Patient AccessPolicy. Confirm both in patient registration setup before testing.
  • Patient sign-in uses the sandbox tenant code. Keep it in public app configuration; it is not a password or secret.
  • The built-in forms manage their own supported MFA behavior. Your route guard uses useSession; it does not authorize individual FHIR operations.
  • A Patient profile is created by the registration workflow. Only add a separate profile-initialization step if your project requires additional fields and the current SDK supports that workflow.

Expected result​

A signed-out person can register or sign in with the sandbox project, and a successful login opens the patient route group. A signed-in person cannot remain on the auth screens, and a signed-out person cannot mount the patient Bluetooth workspace.

Common errors and troubleshooting​

  • Registration returns a project configuration error: confirm patient registration is enabled and the default Patient AccessPolicy exists.
  • The form rejects a valid account: verify the tenant code and API URL belong to the same sandbox project.
  • The route stays on the loading indicator: inspect useSession's error state in a development-only diagnostic screen; do not treat a failed refresh as proof of a valid session.
  • Registration shows an invitation message instead of entering the app: check whether the project routes business-email registrations to clinician invitations and handle that next step intentionally.

Next step​

Continue to step 7: build the measurement screen.