Auth0 dans une app React Native : Expo, iOS et Android

Intégrer Auth0 dans une application mobile React Native avec Expo. PKCE, stockage sécurisé des tokens, biométrie et gestion des sessions hors ligne.

Par Auth0.Expert · 2025-04-05 · 12 min de lecture

React Native Mobile Expo

Auth0 avec React Native et Expo

TL;DR

L'authentification mobile est plus complexe qu'en browser : pas de cookies, tokens stockés dans SecureStore, deep links pour le callback, et biométrie pour le déverrouillage local. Ce guide couvre tout.

---

Installation

La commande suivante installe tous les packages nécessaires à l'authentification sécurisée et biométrique sur Expo.

npx expo install expo-auth-session expo-secure-store expo-local-authentication expo-web-browser expo-crypto

Ce bloc configure les paramètres essentiels pour activer les deep links et FaceID sur iOS dans votre projet Expo.

# Configuration dans app.json
{
  "expo": {
    "scheme": "yourapp",  // Pour les deep links
    "ios": {
      "bundleIdentifier": "com.yourcompany.yourapp",
      "infoPlist": {
        "NSFaceIDUsageDescription": "Pour déverrouiller votre session"
      }
    },
    "android": {
      "package": "com.yourcompany.yourapp"
    }
  }
}

---

Auth0 App : configuration mobile

Voici comment configurer une nouvelle application native sur le dashboard Auth0. Ces paramètres permettent à Auth0 de rediriger vers votre app mobile via deep link lors de l’authentification.

Dashboard → Applications → Create Application → Native

Allowed Callback URLs:
  com.yourcompany.yourapp://callback

Allowed Logout URLs:
  com.yourcompany.yourapp://logout

---

Implémentation Auth0 avec expo-auth-session

Ce code met en place le hook React personnalisé qui gère l'authentification OAuth 2.0 avec Auth0 via le SDK d’Expo. Il assure la gestion du flow Authorization Code + PKCE, le stockage sécurisé des tokens, et le décodage de l’id_token.

// hooks/useAuth0.ts
import * as AuthSession from 'expo-auth-session';
import * as SecureStore from 'expo-secure-store';
import * as Crypto from 'expo-crypto';
import { useState, useCallback } from 'react';

const AUTH0_DOMAIN = 'YOUR_DOMAIN.auth0.com';
const CLIENT_ID = 'YOUR_CLIENT_ID';
const REDIRECT_URI = AuthSession.makeRedirectUri({ scheme: 'yourapp', path: 'callback' });

const discovery = {
  authorizationEndpoint: `https://${AUTH0_DOMAIN}/authorize`,
  tokenEndpoint: `https://${AUTH0_DOMAIN}/oauth/token`,
  revocationEndpoint: `https://${AUTH0_DOMAIN}/oauth/revoke`,
};

export function useAuth() {
  const [tokens, setTokens] = useState(null);
  const [user, setUser] = useState(null);

  const [request, response, promptAsync] = AuthSession.useAuthRequest(
    {
      clientId: CLIENT_ID,
      redirectUri: REDIRECT_URI,
      scopes: ['openid', 'email', 'profile', 'offline_access'],
      responseType: AuthSession.ResponseType.Code,
      usePKCE: true,
    },
    discovery
  );

  // Gestion du callback
  React.useEffect(() => {
    if (response?.type === 'success') {
      exchangeCodeForTokens(response.params.code, request.codeVerifier);
    }
  }, [response]);

  const exchangeCodeForTokens = async (code, codeVerifier) => {
    const tokenResponse = await fetch(`https://${AUTH0_DOMAIN}/oauth/token`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        grant_type: 'authorization_code',
        client_id: CLIENT_ID,
        code,
        code_verifier: codeVerifier,
        redirect_uri: REDIRECT_URI,
      }),
    });
    const tokens = await tokenResponse.json();
    await saveTokens(tokens);
    setTokens(tokens);

    // Décoder l'ID Token
    const payload = JSON.parse(atob(tokens.id_token.split('.')[1]));
    setUser(payload);
  };

  const login = useCallback(() => promptAsync(), [promptAsync]);

  const logout = useCallback(async () => {
    await SecureStore.deleteItemAsync('access_token');
    await SecureStore.deleteItemAsync('refresh_token');
    setTokens(null);
    setUser(null);
  }, []);

  return { user, tokens, login, logout, isLoading: !request };
}

Ce hook gère toute la logique d’authentification mobile Auth0 : il obtient les tokens, les stocke, extrait les informations utilisateur et expose les fonctions login/logout.

---

Stockage sécurisé des tokens

Ce module assure la sauvegarde et la récupération sécurisées des tokens OAuth dans le SecureStore du téléphone, afin de garantir la confidentialité et la persistance de la session.

// utils/tokenStorage.ts
import * as SecureStore from 'expo-secure-store';

const KEYS = {
  ACCESS_TOKEN: 'auth0_access_token',
  REFRESH_TOKEN: 'auth0_refresh_token',
  TOKEN_EXPIRY: 'auth0_token_expiry',
};

export async function saveTokens(tokens: any) {
  await Promise.all([
    SecureStore.setItemAsync(KEYS.ACCESS_TOKEN, tokens.access_token, {
      keychainAccessible: SecureStore.WHEN_UNLOCKED,
    }),
    tokens.refresh_token
      ? SecureStore.setItemAsync(KEYS.REFRESH_TOKEN, tokens.refresh_token, {
          keychainAccessible: SecureStore.WHEN_UNLOCKED,
        })
      : Promise.resolve(),
    SecureStore.setItemAsync(
      KEYS.TOKEN_EXPIRY,
      (Date.now() + tokens.expires_in * 1000).toString()
    ),
  ]);
}

export async function getValidToken(): Promise<string | null> {
  const expiry = await SecureStore.getItemAsync(KEYS.TOKEN_EXPIRY);
  const accessToken = await SecureStore.getItemAsync(KEYS.ACCESS_TOKEN);

  if (expiry && Date.now() < parseInt(expiry) - 60000) {
    return accessToken;
  }

  // Token expiré → refresh
  const refreshToken = await SecureStore.getItemAsync(KEYS.REFRESH_TOKEN);
  if (!refreshToken) return null;

  return refreshAccessToken(refreshToken);
}

Avec ces fonctions, vos tokens OAuth sont stockés en toute sécurité ; la logique gère aussi leur renouvellement automatique via le refresh token.

---

Biométrie : déverrouillage local

Ce code intègre la biométrie et vérifie l'identité de l'utilisateur (empreinte digitale ou FaceID) avant de permettre l'accès à l'application, renforçant la sécurité.

// Vérifier la biométrie avant d'accéder à l'app
import * as LocalAuthentication from 'expo-local-authentication';

async function authenticateWithBiometric(): Promise<boolean> {
  const hasBiometric = await LocalAuthentication.hasHardwareAsync();
  const isEnrolled = await LocalAuthentication.isEnrolledAsync();

  if (!hasBiometric || !isEnrolled) return true; // Pas de biométrie disponible → OK

  const result = await LocalAuthentication.authenticateAsync({
    promptMessage: 'Déverrouillez YourApp',
    fallbackLabel: 'Utiliser le code PIN',
    cancelLabel: 'Annuler',
  });

  return result.success;
}

// Dans App.tsx
function App() {
  const [biometricOk, setBiometricOk] = useState(false);

  useEffect(() => {
    authenticateWithBiometric().then(setBiometricOk);
  }, []);

  if (!biometricOk) return <BiometricPrompt />;
  return <MainApp />;
}

Ce bloc permet d’associer biométrie et navigation principale : l’application ne s'ouvre pleinement qu’après une authentification locale réussie.

---

Deep Link : handle le callback proprement

Pour garantir une expérience fluide après le login Auth0, ce composant traite le retour via deep link et redirige l’utilisateur vers le dashboard après authentification.

// app/callback.tsx (Expo Router)
import { useLocalSearchParams, router } from 'expo-router';
import { useEffect } from 'react';

export default function CallbackScreen() {
  const params = useLocalSearchParams();

  useEffect(() => {
    // Le AuthSession intercepte automatiquement le deep link
    // Cette page ne s'affiche que si le redirect n'est pas intercepté
    router.replace('/dashboard');
  }, []);

  return null;
}

La logique ici sécurise le flux de navigation après redirection OAuth, évitant de rester bloqué sur la page de callback.

---

Push Notifications Auth0 Guardian

Auth0 Guardian permet la double authentification (MFA) par notification push sur mobile. Le code suivant montre comment enregistrer le token push d’un appareil pour recevoir les demandes d’approbation MFA.

// Enregistrer le push token pour MFA
await fetch(`https://${AUTH0_DOMAIN}/api/v1/guardian/enrollments`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${mfaToken}` },
  body: JSON.stringify({
    factor: 'push',
    push_credentials: {
      token: expoPushToken,
      name: 'iPhone de Jean',
    },
  }),
});

---

> 📱 App mobile + Auth0 : nos équipes React Native livrent une authentification mobile sécurisée avec biométrie, deeplinks et gestion offline en 2 semaines.

📞 +33 1 23 45 67 89 · 📧 [email protected]


Autres articles