← Retour au blog
FirebaseAngularAuthenticationFirestoreWeb Hosting

Firebase + Angular : authentification, Firestore et hosting en production

Firebase est devenu la solution par défaut pour les équipes Angular solo ou petites qui veulent éviter de maintenir une API backend. Mais passer de la démo au code production révèle rapidement des pièges : gestion d'état d'authentification fragmentée, règles Firestore trop permissives, ou hosting configuré sans cache strategy. Cet article détaille l'architecture que nous utilisons en production, des patterns réactifs aux optimisations de déploiement.

Authentification Firebase : au-delà du login de base

L'erreur classique est de considérer Firebase Auth comme un simple wrapper sur signInWithEmailAndPassword() . En réalité, vous gérez un état global complexe : l'utilisateur peut être non-authentifié, en cours de chargement, authentifié avec un token expiré, ou avoir une session persistée. Déployer ce flux sans architecture réactive crée rapidement des races conditions et des fuites mémoire.

La bonne approche consiste à centraliser cet état via un service observant authStateChanged() dès le bootstrap de l'app. Nous utilisons une approche hybrid : un signal Angular pour le user courant (rapide, synchrone pour les templates) et un Observable pour les changements d'état (intégration avec les guards et interceptors).

// auth.service.ts
export class AuthService {
  private auth = inject(Auth);
  private db = inject(Firestore);
  
  userSignal = signal<User | null>(null);
  isLoading = signal(true);
  
  constructor() {
    onAuthStateChanged(this.auth, async (user) => {
      if (user) {
        // Charger les données utilisateur depuis Firestore
        const docSnap = await getDoc(doc(this.db, 'users', user.uid));
        this.userSignal.set({ ...user, profile: docSnap.data() });
      } else {
        this.userSignal.set(null);
      }
      this.isLoading.set(false);
    });
  }
  
  signUp(email: string, password: string) {
    return createUserWithEmailAndPassword(this.auth, email, password)
      .then(cred => this.createUserProfile(cred.user));
  }
  
  private createUserProfile(user: User) {
    return setDoc(doc(this.db, 'users', user.uid), {
      email: user.email,
      createdAt: serverTimestamp(),
      role: 'user'
    });
  }
}

Cette approche offre plusieurs avantages : les templates peuvent utiliser authService.userSignal() pour l'affichage sans subscribe (les signaux sont auto-tracked), les guards réagissent aux changements d'état via effect() , et les interceptors HTTP peuvent injecter le token sans complexity. Le point clé est d'initialiser authStateChanged() au niveau root et de ne jamais laisser ce flux se terminer prématurément.

Firestore : architecture des données et règles de sécurité

Firestore brille par sa simplicité, mais c'est aussi son piège : il est facile de commencer sans structure, puis de se retrouver avec des règles trop permissives ou des requêtes N+1. La première étape est de modéliser correctement vos collections en pensant aux patterns de lecture. Évitez les documents monolithiques ; préférez une denormalization intelligente couplée à des sous-collections pour les relations 1-N.

Les règles Firestore doivent être écrites avant le code client, pas après. Voici un template sécurisé pour une app type : chaque utilisateur gère ses données personnelles, peut lire les profils publics des autres, et un admin a un accès complet. Le piège courant est de tester en mode développement (tout autorisé) et d'oublier d'activer les règles avant la prod. Nous avons vu des apps exposer des données sensibles juste parce que les règles restaient en allow read, write: if false .

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Utilisateurs
    match /users/{uid} {
      allow read: if request.auth.uid == uid || get(/databases/$(database)/documents/users/$(uid)).data.isPublic == true;
      allow write: if request.auth.uid == uid;
      
      // Sous-collection : posts privés
      match /posts/{postId} {
        allow read, write: if request.auth.uid == uid;
      }
    }
    
    // Documents publics (lisibles par tous)
    match /public/{docId} {
      allow read: if true;
      allow write: if request.auth.uid == resource.data.authorId && resource.data.authorId == request.auth.uid;
    }
    
    // Admin only
    match /admin/{docId} {
      allow read, write: if request.auth.uid != null && exists(/databases/$(database)/documents/users/$(request.auth.uid)) && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
    }
  }
}

Côté Angular, exploitez la réactivité : créez des services qui encapsulent la logique Firestore et retournent des Observables ou des Signals. Utilisez collectionData() avec where() , limit() et orderBy() pour paginer efficacement. Les indexes composites sont nécessaires dès qu'on combine plusieurs conditions ; Firebase vous suggère les créer via la console lorsqu'une requête les demande. Enfin, mettez en place une stratégie de cache côté client (via NgRx ou des services locaux) pour éviter de re-fetcher les mêmes données.

Hosting Firebase : configuration, caching et déploiement

Le hosting Firebase est excellent pour les SPAs Angular, mais requiert une configuration précise. Par défaut, les fichiers statiques sont cachés agressivement (30 jours), ce qui est dangereux si vous ne versionnez pas vos assets. La bonne pratique : laisser le cache par défaut pour les fichiers hachés (main.abc123.js, styles.def456.css), mais définir Cache-Control: no-cache pour index.html et les fichiers de configuration.

Voici la configuration Firebase recommandée :

{
  "hosting": {
    "public": "dist/my-app",
    "ignore": ["firebase.json", ".firebaserc"],
    "headers": [
      {
        "source": "index.html",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "no-cache, no-store, must-revalidate"
          }
        ]
      },
      {
        "source": "**/*.@(js|css|woff2)",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "public, max-age=31536000, immutable"
          }
        ]
      }
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

Les rewrites sont critiques : elles redirigent toutes les routes vers index.html pour que Angular Router gère la navigation. Sans cela, les accès directs à /dashboard retournent 404. Configurez aussi les headers CORS si vous servez une API (bien que Firebase Cloud Functions soit plus idiomatique). Pour le déploiement, intégrez Firebase CLI dans votre CI/CD : firebase deploy --only hosting après un build Angular en mode production avec ng build --configuration production . Versionnez votre .firebaserc pour que tous les développeurs ciblent le bon projet Firebase.

Pièges courants et antipatterns

Le piège numéro un : ne pas gérer les erreurs d'authentification ou de Firestore proprement. Les utilisateurs voient une app cassée au lieu d'un message clair. Toujours wrapper les appels Firebase dans des try-catch ou des opérateurs RxJS comme catchError() . Deuxième piège : oublier que onAuthStateChanged() se déclenche au rechargement, d'où l'importance du isLoading signal pour éviter les redirects prématurés. Troisième : ne pas tester les règles Firestore en mode émulateur local avant la prod. Firebase propose une excellente suite d'émulation ; l'utiliser économise des bugs coûteux.

Un autre antipattern courant est de stocker des tokens manuellement. Firebase gère cela automatiquement via les cookies de session (en mode web), donc n'essayez pas d'extraire et de persister le token JWT vous-même, sauf si vous avez une bonne raison (ex. : app Ionic/Capacitor avec stockage natif). Enfin, ne pas optimiser les bundles : Firebase Admin SDK ne doit jamais être inclus côté client. Utilisez seulement le SDK de compatibilité (compat) ou le SDK modulaire pour web.

Conclusion : architecture solide et maintenabilité

Une intégration Firebase + Angular production-ready repose sur trois piliers : une gestion d'état d'auth centralisée et réactive, des règles Firestore bien modélisées et testées, et une stratégie de caching/déploiement clairement définie. Investir quelques jours dans cette fondation évite des semaines de débogage et de refactorisation plus tard. Utilisez les signaux pour la performance, les Observables pour l'asynchrone complexe, et n'oubliez jamais que Firebase est un service géré : testez localement avec l'émulateur, ne faites jamais confiance aux règles par défaut, et versionnez votre configuration. Avec cette approche, vous pouvez itérer rapidement tout en gardant la confiance dans la sécurité et la scalabilité.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️