cda-app-mobile-reactnative
Application mobile React Native (Expo) avec authentification, navigation privée/publique et gestion complète du cycle de session utilisateur (login, tokens, refresh, profil, déconnexion). Le projet démontre la mise en place d'un socle mobile réutilisable avec une séparation claire des écrans métier et des flux d'authentification.
🎯 Contexte et objectifs
- Développer une application mobile cross-platform orientée API avec parcours d’authentification sécurisé.
- Mettre en place un flux UX complet: connexion/inscription, navigation tabulée privée, consultation/recherche d’utilisateurs.
- Structurer la couche d’intégration réseau et la persistance locale des credentials pour assurer la continuité de session.
🛠️ Réalisations
🧩 Conception
- Le socle applicatif combine Expo/React Native, React Navigation, AsyncStorage, Axios et validation de formulaires (Formik/Yup). Source: cda-app-mobile-reactnative/package.json
{
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.15.9",
"@react-navigation/bottom-tabs": "^6.0.7",
"@react-navigation/native": "^6.0.4",
"@react-navigation/stack": "^6.0.9",
"axios": "^0.22.0",
"expo": "~42.0.1",
"formik": "^2.2.9",
"react-native-elements": "^3.4.2",
"yup": "^0.32.9"
}
}
- L’architecture d’état repose sur un
useReducerd’authentification avec transitions explicites (LOGIN,LOGOUT,REFRESH_TOKEN,RETRIEVE_TOKEN). Source: cda-app-mobile-reactnative/App.js
const initialLoginState = {
isLoading: false,
userName: null,
userToken: null,
refreshToken: null,
xsrfToken: null,
email: null,
};
const loginReducer = (prevState, action) => {
switch (action.type) {
case "RETRIEVE_TOKEN":
return { ...prevState, userName: action.username, userToken: action.token, isLoading: false };
case "REFRESH_TOKEN":
return { ...prevState, xsrfToken: action.xsrfToken, userToken: action.token, refreshToken: action.refreshToken, isLoading: false };
case "LOGIN":
return { ...prevState, xsrfToken: action.xsrfToken, userToken: action.token, refreshToken: action.refreshToken, email: action.email, isLoading: false };
case "LOGOUT":
return { ...prevState, userName: null, xsrfToken: null, userToken: null, refreshToken: null, email: null, isLoading: false };
}
};
💻 Développement
- Backend :
Le client mobile encapsule les appels API d’authentification (
/auth/login,/auth/token,/auth/me) et injecte dynamiquementAuthorization+x-xsrf-token. Source: cda-app-mobile-reactnative/utils/api.js
const instance = axios.create({
baseURL: API_BASE_URL,
});
export const addXsrfToken = async (token) => {
instance.defaults.headers.common["Authorization"] = "Bearer " + token;
const xsrfToken = await AsyncStorage.getItem("xsrfToken");
instance.defaults.headers.common["x-xsrf-token"] = xsrfToken;
};
Le flux de login persiste accessToken, refreshToken, xsrfToken et email dans AsyncStorage avant mise à jour du contexte global.
Source: cda-app-mobile-reactnative/App.js
const response = await client.post("auth/login", {
...foundUser.userInfo,
});
if (response.status === 200) {
await AsyncStorage.setItem("email", email);
await AsyncStorage.setItem("userToken", response.data.accessToken);
await AsyncStorage.setItem("xsrfToken", response.data.xsrfToken);
await AsyncStorage.setItem("refreshToken", response.data.refreshToken);
}
dispatch({
type: "LOGIN",
xsrfToken: xsrfToken,
token: userToken,
refreshToken: refreshToken,
email: email,
});
La stratégie de refresh déclenche un renouvellement via /auth/token en cas d’expiration (401) puis remet à jour les tokens persistés.
Source: cda-app-mobile-reactnative/App.js
if (err.response.data.statusCode === 401) {
refreshToken = await AsyncStorage.getItem("refreshToken");
email = await AsyncStorage.getItem("email");
const refresh = await client.post("auth/token", {
email: email,
refresh_token: refreshToken,
});
await AsyncStorage.setItem("refreshToken", refresh.data.refreshToken);
await AsyncStorage.setItem("userToken", refresh.data.accessToken);
await AsyncStorage.setItem("xsrfToken", refresh.data.xsrfToken);
dispatch({
type: "REFRESH_TOKEN",
xsrfToken: refresh.data.xsrfToken,
userToken: refresh.data.accessToken,
refreshToken: refresh.data.refreshToken,
});
}
- Frontend : La navigation imbrique Stack + Bottom Tabs et bascule automatiquement entre routes publiques et privées selon la présence du token. Source: cda-app-mobile-reactnative/App.js
<NavigationContainer>
<Stack.Navigator>
{loginState.userToken !== null ? (
<Stack.Screen
options={{ headerShown: false }}
name="HomeTabs"
component={HomeTabs}
/>
) : (
<>
<Stack.Screen options={{ headerShown: false }} name="Login" component={Login} />
<Stack.Screen options={{ headerShown: true }} name="Register" component={Register} />
</>
)}
</Stack.Navigator>
</NavigationContainer>
L’écran d’inscription utilise Formik + Yup pour valider les champs (email, longueur mot de passe, confirmation), puis envoie un payload normalisé. Source: cda-app-mobile-reactnative/Screens/Register.js
const validationSchema = Yup.object({
firstname: Yup.string().trim().min(3, "Invalid name!").required("Name is required!"),
email: Yup.string().email("Invalid email!").required("Email is required!"),
password: Yup.string().trim().min(6, "Password is too short!").required("Password is required!"),
confirmPassword: Yup.string().equals([Yup.ref("password"), null], "Password does not match!"),
});
const signUpForm = async (values, formikActions) => {
const clone = (({ confirmPassword, ...o }) => o)(values);
signUp({ clone });
formikActions.resetForm();
};
La feature Search implémente une recherche par email via API et un rendu conditionnel des informations utilisateur.
Source: cda-app-mobile-reactnative/Screens/Search.js
const [searchEmail, setSearchEmail] = useState("");
const [userFind, setUserFound] = useState({});
const search = async (email) => {
const userEmail = await client.get("users/email/" + email);
setUserFound(userEmail.data);
};
<Text>{userFind.firstname ? "Prénom " + userFind.firstname : ""}</Text>
<Text>{userFind.lastname ? "Nom " + userFind.lastname : ""}</Text>
<Text>{userFind.username ? "Pseudo " + userFind.username : ""}</Text>
🏗️ DevOps & Qualité
- Le projet formalise une logique de validation locale réutilisable pour les formulaires (email, champs obligatoires, gestion d’erreur utilisateur temporaire). Source: cda-app-mobile-reactnative/utils/methods.js
export const isValidObjField = (obj) => {
return Object.values(obj).every((value) => value.trim());
};
export const updateError = (error, stateUpdater) => {
stateUpdater(error);
setTimeout(() => {
stateUpdater("");
}, 2500);
};
export const isValidEmail = (value) => {
const regx = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
return regx.test(value);
};
📈 Résultats
- Sur la base de l’historique Git du dépôt analysé, le travail couvre la période 2021-10-05 -> 2026-02-14, avec 18 commits réalisés par 2 contributeurs. Le projet délivre une application mobile avec session persistée, rafraîchissement de tokens, navigation protégée et écrans connectés à l’API utilisateurs. La structure contexte + reducer + stockage local permet de maintenir une expérience fluide même avec expiration de session. Le bénéfice technique est une base mobile robuste, directement exploitable pour des usages authentifiés en environnement réel.
🔧 Environnement technique
- Mobile: React Native, Expo, React Navigation (Stack + Bottom Tabs), React Native Elements.
- Données/API: Axios, AsyncStorage, headers
Authorization/x-xsrf-token. - Formulaires: Formik, Yup, validation utilitaire custom.
- Architecture: AuthContext, reducer de session, écrans modulaires (
Login,Register,Home,Search,Profile). - Outillage: scripts Expo (
start,android,ios,web,eject).