Example
This guide provides an example that demonstrates how to implement authentication and protect routes with Expo Router using @flexnative/authentication
.
This example it is based on Authentication in Expo Router form expo officially documentation by using the technique of Using React Context and Route Groups.
For a full example in a real case fallow this repository.
Consider the following project structure that has a /sign-in
route that is always accessible and a (app)
group that requires authentication:
AuthProvider
To follow the above example, set up a React Context provider that can expose an authentication session to the entire app. You can implement your custom authentication session provider or use the one from the example below.
import React from "react";
import { Alert } from "react-native";
import * as LocalAuthentication from "expo-local-authentication";
import { AuthContext, useAuthState } from "@flexnative/authentication";
const SESSION_KEY = 'token';
const AuthProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
const [[isLoading, session], setSession] = useAuthState(SESSION_KEY);
const handleAuthenticate = async () => {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
if (!hasHardware) {
Alert.alert("Error", "No biometric data is enrolled on this device.");
return;
}
const enrolled = await LocalAuthentication.isEnrolledAsync();
if (!enrolled) {
Alert.alert("Error", "No biometric data is enrolled on this device.");
return;
}
const result = await LocalAuthentication.authenticateAsync({
promptMessage: "Authenticate with fingerprint",
fallbackLabel: "Use passcode",
});
if (result.success) {
// here you can can get the email from SecureStore
// and get token from authorization server.
setSession('SESSION_ID');
} else {
Alert.alert("Error", "Authentication failed.");
}
};
const handleLogin = async (loginForm: LoginProps) => {
// here you can call your authorization server.
setSession(token);
}
const handleLogout = async () => {
setSession(null);
}
return (
<AuthContext.Provider value={{
state: {
session: session!,
authenticated: session !== null
},
onLogin: handleLogin,
onLogout: handleLogout,
onAuthentication: handleAuthenticate,
}}>
{children}
</AuthContext.Provider>
);
};
export default AuthProvider;
app/_layout.tsx
Use the AuthProvider
in the root layout to provide the authentication context to the entire app.
It's imperative that the <Slot />
is mounted before any navigation events are triggered.
Otherwise, a runtime error will be thrown.
import { Slot } from 'expo-router';
import AuthProvider from '/contexts/AuthProvider';
export default function Root() {
return (
<AuthProvider>
<Slot />
</AuthProvider>
);
}
app/(app)/_layout.tsx
Create a nested layout route that checks whether users are authenticated before rendering the child route components. This layout route redirects users to the sign-in screen if they are not authenticated.
import { Redirect, Slot } from 'expo-router';
import { useAuthContext } from '@flexnative/authentication';
export default function AppLayout() {
const { state } = useAuthContext();
if (!state.authenticated) {
return <Redirect href="/login" />;
}
return (
<Slot />
);
}
app/sign-in.tsx
Create the /sign-in
screen.
It can toggle the authentication using onLogin
.
Since this screen is outside the (app)
group, the group's layout and authentication check do not run when rendering this screen.
This lets logged-out users see this screen.
import { router } from 'expo-router';
import { Text, View } from 'react-native';
const auth = useAuthContext();
export default function SignIn() {
const { signIn } = useSession();
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text
onPress={() => {
auth.onLogin(loginForm);
// Navigate after signing in. You may want to tweak this to ensure sign-in is
// successful before navigating.
router.replace('/');
}}>
Sign In
</Text>
</View>
);
}
app/(app)/index.tsx
Implement an authenticated screen that lets users sign out.
export default function Index() {
const { auth } = useAuthContext();
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text
onPress={() => {
// The `app/(app)/_layout.tsx` will redirect to the sign-in screen.
auth.onLogout();
}}>
Sign Out
</Text>
</View>
);
}