Fermer

mai 21, 2018

Authentification Firebase et Angular avec Auth0: Partie 1 –


Cet article a été publié à l'origine sur le blog Auth0.com et est republié ici avec sa permission.

Dans cette série de didacticiels en deux parties, nous apprendrons comment créer une application qui sécurise une extrémité arrière de nœud et une extrémité avant angulaire avec authentification Auth0 . Notre serveur et notre application authentifient également une base de données Cloud Firestore Firebase avec des jetons personnalisés afin que les utilisateurs puissent laisser des commentaires en temps réel de manière sécurisée après s'être authentifiés avec Auth0.

Le code d'application Angular peut être trouvé dans le repo GitHub angulaire-firebase et l'API Node dans le repo firebase-auth0-nodeserver .

Authentification de Firebase et Angular avec Auth0: Part 1 [19659006] La première partie de notre tutoriel couvrira:

  1. Firebase et Auth0
  2. Ce que nous allons construire
  3. Angular CLI
  4. Client Auth0 et API
  5. Projet Firebase avec compte de service
  6. API nœud
  7. Configuration de l'application angulaire
  8. Architecture de l'application angulaire
  9. Implémentation des modules partagés
  10. Implémentation des modules de routage et chargement par chargement
  11. Chargement et erreur des composants
  12. Logique d'authentification
  13. Logique du noyau
  14. Prochaines étapes [19659021] Firebase et Auth0

Fireba se est une plateforme de développement d'applications mobiles et Web. Firebase a été acquise par Google en 2014 et continue d'être développée sous l'égide de Google. Firebase fournit des bases de données NoSQL ( RTDB, ou Realtime Database et Cloud Firestore, en version bêta au moment de la rédaction ) hébergées dans le cloud et connectées via des sockets Web pour fournir des fonctionnalités en temps réel aux applications.

Auth0 est une plate-forme basée sur le cloud qui fournit l'authentification et l'autorisation en tant que service. En tant que fournisseur d'authentification, Auth0 permet aux développeurs d'implémenter et de personnaliser facilement la sécurité de connexion et d'autorisation pour leurs applications.

Choisir Auth0 + Firebase Authentication

Si vous connaissez déjà les offres de Firebase, demandez-vous: implémenter Auth0 avec des jetons personnalisés dans Firebase au lieu de coller avec l'authentification intégrée de Firebase par lui-même?

Tout d'abord, il y a une distinction importante à faire ici. L'utilisation de Auth0 pour sécuriser Firebase ne signifie pas que vous n'êtes pas en utilisant Firebase auth. Firebase a une approche d'authentification personnalisée qui permet aux développeurs d'intégrer leur solution d'identité préférée à Firebase auth. Cette approche permet aux développeurs d'implémenter l'authentification Firebase pour qu'elle fonctionne de manière transparente avec les systèmes propriétaires ou d'autres fournisseurs d'authentification.

Il existe de nombreuses raisons potentielles pour lesquelles nous souhaitons intégrer Auth0 à l'authentification Firebase. Alternativement, il existe des scénarios où l'utilisation de base Firebase auth par elle-même pourrait suffire. Explorons.

Vous pouvez utiliser l'authentification intégrée de Firebase par elle-même si vous:

  • Vous ne voulez authentifier que Firebase RTDB ou Firestore et n'avez pas besoin d'authentifier les backend supplémentaires
  • une petite poignée d'options de connexion et pas besoin de fournisseurs d'identité d'entreprise, intégration avec vos propres bases de données de stockage, etc.
  • Pas besoin d'une gestion étendue des utilisateurs, enrichissement de profil, etc. API
  • Pas besoin de personnaliser les flux d'authentification
  • Vous n'avez pas besoin d'adhérer aux règles de conformité concernant le stockage des données utilisateur.

Vous devriez considérer Auth0 avec un jeton Firebase personnalisé si vous:

Essentiellement, les fournisseurs d'authentification de base de Firebase devraient suffire si vous avez un application simple avec des besoins d'authentification bare-bones et sont seulement en utilisant des bases de données Firebase. Cependant, si vous avez besoin de plus que cela, Firebase offre un excellent moyen d'utiliser leurs services avec autres solutions d'authentification . C'est un scénario beaucoup plus réaliste auquel de nombreux développeurs seront confrontés, nous allons donc l'explorer en détail ici.

Ce que nous allons construire

Nous allons construire une API Node.js sécurisée avec Auth0 Mets des jetons Firebase personnalisés et renvoie également des données sur dix races de chiens différentes.

Nous allons également construire une application frontale Angular appelée "Dogs populaires" qui affiche des informations sur les dix chiens les plus populaires en 2016, classés par popularité publique par l'American Kennel Club (AKC). Notre application sera sécurisée par Auth0, appelez l'API Node pour récupérer les données du chien et appelez l'API pour acquérir des jetons Firebase afin d'autoriser les utilisateurs à ajouter et supprimer des commentaires en temps réel avec Cloud Firestore. L'application utilisera des modules partagés et implémentera un chargement paresseux.

 Angulaire Firebase app avec des jetons personnalisés Auth0

Pour implémenter l'application, vous aurez besoin des éléments suivants:

  • CLI angulaire
  • ] Un compte Auth0 gratuit avec un client et une API configurée
  • Un projet Firebase gratuit avec un compte de service

Commençons!

Angular CLI

Assurez-vous d'avoir Node.js avec NPM installé sur votre machine locale. Exécutez la commande suivante pour installer l'interface CLI Angular globalement:

 $ npm install -g @ angular / cli @ latest

Nous allons générer notre application Angular et presque toute son architecture en utilisant la CLI.

Auth0 Client et API

Vous aurez besoin d'un compte Auth0 pour gérer l'authentification. Vous pouvez créer un compte gratuit ici .

 Auth0 écran de connexion

Ensuite, configurez une application client et une API Auth0 afin que Auth0 puisse s'interfacer avec l'application Angular et API de noeud

Configurer un client Auth0

  1. Accédez à votre tableau de bord Auth0 et cliquez sur le bouton Créer un nouveau client .
  2. Nommez votre nouvelle application (quelque chose comme Angular Firebase ) et sélectionnez Applications Web page unique .
  3. Dans les paramètres pour votre nouvelle application client Auth0, ajoutez http: // localhost : 4200 / callback aux URL de rappel autorisées .
  4. Activer le bouton de basculement pour Utilisez Auth0 au lieu de l'IdP pour activer l'authentification unique . de la section Paramètres cliquez sur "Afficher les paramètres avancés". Choisissez l'onglet OAuth et vérifiez que l'algorithme de signature JsonWebToken est défini sur "RS256".
  5. Si vous le souhaitez, vous pouvez établir des connexions sociales . Vous pouvez ensuite les activer pour votre application dans les options Client sous l'onglet Connexions . L'exemple montré dans la capture d'écran ci-dessus utilise le nom d'utilisateur / mot de passe, Facebook, Google et Twitter.

Note: Pour la production, assurez-vous de créer vos propres clés sociales et ne laissez pas les connexions sociales utilisées.

Configuration d'une API Auth0

  1. Accédez aux API dans votre tableau de bord Auth0 et cliquez sur le bouton "Créer une API". Entrez un nom pour l'API, par exemple API Firebase Dogs . Définissez l'identificateur sur l'URL de votre point de terminaison API. Dans ce tutoriel, notre identifiant d'API est http: // localhost: 1337 / . L'algorithme de signature doit être "RS256".
  2. Vous pouvez consulter l'exemple Node.js sous l'onglet Quick Start dans les paramètres de votre nouvelle API. Dans les prochaines étapes, nous allons implémenter notre API Node de cette manière en utilisant Express express-jwt et jwks-rsa . Nous sommes maintenant prêts à implémenter l'authentification Auth0 à la fois sur notre client angulaire et notre API back-end de nœud

    Projet Firebase avec compte de service

    Ensuite, vous aurez besoin d'un projet gratuit Firebase . Projet Firebase

    1. Accédez à la Firebase Console et connectez-vous avec votre compte Google
    2. Cliquez sur Ajouter un projet .
    3. Dans la boîte de dialogue qui s'affiche, donnez votre projeter un nom (tel que Angular Firebase Auth0 ). Un identifiant de projet sera généré en fonction du nom que vous avez choisi. Vous pouvez ensuite sélectionner votre pays / région.
    4. Cliquez sur le bouton Créer un projet

    Générer une clé SDK administrateur

    Pour créer des jetons Firebase personnalisés vous Vous devez accéder au SDK Firebase Admin . Pour obtenir l'accès, vous devez créer un compte de service dans votre nouveau projet Firebase.

    Cliquez sur l'icône en forme de roue dentée à côté de la vue d'ensemble du projet dans la barre latérale de la console Firebase et sélectionnez Paramètres du projet . :

     Paramètre du projet Firebase

    Dans la vue des paramètres, cliquez sur l'onglet Comptes de service . L'interface utilisateur Firebase Admin SDK apparaît, affichant un extrait de code de configuration. Node.js est sélectionné par défaut. C'est la technologie que nous voulons, et nous l'implémenterons dans notre API Node. Cliquez sur le bouton Générer une nouvelle clé privée

    Une boîte de dialogue s'affiche pour vous avertir de conserver votre clé privée confidentiellement. Nous veillerons à ne jamais vérifier cette clé dans un dépôt public. Cliquez sur le bouton Generate Key pour télécharger la clé sous la forme d'un fichier .json . Nous allons bientôt ajouter ce fichier à notre API Node.

    API Node

    L'API Node.js terminée pour ce tutoriel se trouve sur le firebase-auth0-nodeserver GitHub repo . Apprenons à créer cette API.

    Structure du fichier de l'API de noeud

    Nous allons configurer la structure de fichier suivante:

     firebase-auth0-nodeserver /
      | --firebase /
         | -. gitignore
         | -  .json
      | -. gitignore
      | --config.js
      | --dogs.json
      | --package.json
      | --routes.js
      | --server.js
    

    Vous pouvez générer les dossiers et les fichiers nécessaires avec la ligne de commande comme suit:

     $ mkdir firebase-auth0-nodeserver
    $ cd firebase-auth0-nodeserver
    $ mkdir firebase
    $ touch firebase / .gitignore
    $ touch .gitignore
    $ touch config.js
    $ touch dogs.json
    $ touch package.json
    $ touch routes.js
    $ touch server.js
    

    Clé SDK Admin Firebase et Ignorer Git

    Maintenant, déplacez le fichier de clé Firebase Admin SDK .json que vous avez téléchargé précédemment dans le dossier firebase . Nous veillerons à ce que le dossier soit archivé, mais son contenu ne sera jamais envoyé à un repo en utilisant le firebase / .gitignore comme ceci:

     # firebase /. gitignore
    *
    * /
    ! .gitignore
    

    Cette configuration .gitignore garantit que Git ignorera tous les fichiers et dossiers du répertoire firebase sauf pour le fichier .gitignore lui-même. Cela nous permet de valider un dossier (essentiellement) vide. Notre clé SDK .json Firebase Admin peut vivre dans ce dossier et nous n'aurons pas à nous inquiéter du gitignoring par nom de fichier .

    Note: Ceci est particulièrement utile si le projet est arrêté sur plusieurs machines et que des clés différentes (avec des noms de fichiers différents) sont générées.

    Ajoutons maintenant le code du répertoire racine .gitignore :

     #. gitignore
    config.js
    node_modules
    

    Dogs JSON Data

    Nous allons maintenant ajouter les données pour dix races de chiens. Par souci de concision, vous pouvez simplement copier et coller ces données dans votre fichier dogs.json .

    Dépendances

    Ajoutons notre fichier package.json comme ça:

     {
      "name": "firebase-auth0-nodeserver",
      "version": "0.1.0",
      "description": "serveur Node.js qui s'authentifie avec un jeton d'accès Auth0 et renvoie un jeton d'authentification Firebase.",
      "référentiel": "https://github.com/auth0-blog/firebase-auth0-nodeserver",
      "main": "server.js",
      "scripts": {
        "démarrer": "serveur de noeud"
      },
      "auteur": "Auth0",
      "licence": "MIT",
      "dépendances": {},
      "devDependencies": {}
    }
    

    Nous installerons les dépendances avec la ligne de commande et les dernières versions seront sauvegardées automatiquement dans le fichier package.json :

     $ npm install --save body-parser cors express express-jwt jwks-rsa firebase-admin
    

    Nous aurons besoin de body-parser cors et express pour servir nos points de terminaison API. L'authentification s'appuiera sur express-jwt et jwks-rsa tandis que le jeton de jetons Firebase est implémenté avec le SDK firebase-admin (auquel nous aurons accès) en utilisant la clé que nous avons générée.

    Configuration

    Dans le fichier config.js ajoutez le code suivant et remplacez les valeurs de l'espace réservé par vos propres paramètres:

     // config.js
    module.exports = {
      AUTH0_DOMAIN: '', // par exemple, you.auth0.com
      AUTH0_API_AUDIENCE: '', // par exemple, http: // localhost: 1337 /
      FIREBASE_KEY: './firebase/', // par exemple, votre-projet-firebase-adminsdk-xxxxx-xxxxxxxxxx.json
      FIREBASE_DB: '' // par exemple, https://your-project.firebaseio.com
    }
    

    Server

    Avec nos données, notre configuration et nos dépendances en place, nous pouvons maintenant implémenter notre serveur Node. Ouvrez le fichier server.js et ajoutez:

     // server.js
    // Modules
    const express = require ('express');
    const bodyParser = require ('body-parser');
    const cors = require ('cors');
    
    // App
    const app = express ();
    app.use (bodyParser.json ());
    app.use (bodyParser.urlencoded ({extended: false}));
    app.use (cors ());
    
    // Définir le port
    port const = process.env.PORT || «1337»;
    app.set ('port', port);
    
    // Routes
    require ('./ routes') (app);
    
    // Serveur
    app.listen (port, () => console.log (`Serveur s'exécutant sur localhost: $ {port}`));
    

    Ceci lancera notre serveur Node avec Express à http: // localhost: 1337 / .

    Note: Notez qu'il s'agit de l'identifiant API que nous avons configuré dans Auth0. [19659121] API Routes

    Ensuite, ouvrez le fichier routes.js . C'est ici que nous définirons nos extrémités d'API, les sécuriserons et frapperons les jetons Firebase personnalisés. Ajoutez le code suivant:

     // routes.js
    // Dépendances
    const jwt = require ('express-jwt');
    const jwks = require ('jwks-rsa');
    const firebaseAdmin = require ('firebase-admin');
    // Config
    const config = require ('./ config');
    
    module.exports = function (app) {
      // middleware d'authentification Auth0
      const jwtCheck = jwt ({
        secret: jwks.expressJwtSecret ({
          cache: vrai,
          rateLimit: true,
          jwksRequestsPerMinute: 5,
          jwksUri: `https: // $ {config.AUTH0_DOMAIN} / .well-known / jwks.json`
        }),
        audience: config.AUTH0_API_AUDIENCE,
        émetteur: `https: // $ {config.AUTH0_DOMAIN} /`,
        algorithme: 'RS256'
      });
    
      // Initialise Firebase Admin avec un compte de service
      const serviceAccount = require (config.FIREBASE_KEY);
      firebaseAdmin.initializeApp ({
        informations d'identification: firebaseAdmin.credential.cert (serviceAccount),
        databaseURL: config.FIREBASE_DB
      });
    
      // Objet GET contenant un jeton personnalisé Firebase
      app.get ('/ auth / firebase', jwtCheck, (req, res) => {
        // Crée un UID à partir de l'utilisateur Auth0 authentifié
        const uid = req.user.sub;
        // Jeton de menthe utilisant Firebase Admin SDK
        firebaseAdmin.auth (). createCustomToken (uid)
          .then (customToken =>
            // La réponse doit être un objet ou des erreurs Firebase
            res.json ({firebaseToken: customToken})
          )
          .catch (err =>
            res.status (500) .send ({
              message: 'Quelque chose s'est mal passé lors de l'acquisition d'un jeton Firebase.',
              erreur: err
            })
          )
      });
    
      // Configurer les données JSON des chiens pour l'API
      const chiens = require ('./ dogs.json');
      const getDogsBasic = () => {
        const chiensBasicArr = dogs.map (chien => {
          revenir {
            classement: dog.rank,
            race: dog.breed,
            image: dog.image
          }
        });
        retour chiensBasicArr;
      }
    
      // GET chiens (public)
      app.get ('/ api / dogs', (req, res) => {
        res.send (getDogsBasic ());
      });
    
      // Obtenir les détails du chien par rang (privé)
      app.get ('/ api / chien /: rang', jwtCheck, (req, res) => {
        const rank = req.params.rank * 1;
        const thisDog = dogs.find (chien => dog.rank === rang);
        res.send (thisDog);
      });
    }
    

    À un niveau élevé, notre fichier d'itinéraires effectue les opérations suivantes:

    • Configure la vérification d'authentification pour s'assurer que seuls les utilisateurs connectés peuvent accéder aux routes avec middleware jwtCheck
    • Initialise le SDK Admin Firebase avec la clé privée générée à partir du compte de service de projet Firebase
    • Fournit un point de terminaison sécurisé GET qui renvoie un jeton Firebase personnalisé
    • Fournit un point de terminaison public GET * qui renvoie une version courte des données de chiens
    • Fournit un point de terminaison sécurisé GET * qui renvoie les données détaillées d'un chien spécifique, demandées par rang.

    * Les points de terminaison utilisent des variations du même ensemble de données de base pour simuler un

    Vous pouvez lire les commentaires de code pour plus de détails.

    Servir l'API

    Vous pouvez servir l'API Node en exécutant:

     $ node server
    

    L'API sera alors disponible à http: // localhost: 1337 .

    Note: Si vous essayez d'accéder à des routes sécurisées dans le navigateur, vous devriez recevoir un 401 Erreur non autorisée .

    C'est tout pour notre serveur! Laissez l'API s'exécuter pour qu'elle soit accessible à l'application Angular, que nous installerons ensuite

    Set Up Angular App

    Il est maintenant temps de créer notre application Angular et de configurer des dépendances supplémentaires. [19659140] Créer une nouvelle application angulaire

    Vous devriez déjà avoir installé la CLI angulaire plus tôt. Nous pouvons maintenant utiliser la CLI pour générer notre projet et son architecture. Pour créer une nouvelle application, choisissez un dossier contenant, puis exécutez la commande suivante:

     $ ng new angular-firebase --routing --skip-tests
    

    L'indicateur - routing génère une application avec un module de routage et - skip-tests génère le composant racine sans fichier .spec.ts

    Note: Par souci de concision, nous n'allons pas couvrir les tests dans cet article. Si vous souhaitez en savoir plus sur les tests dans Angular, consultez la conclusion du didacticiel pour plus de ressources.

    Installer les dépendances frontales

    Maintenant, installons nos dépendances frontales:

     $ cd angular-firebase
    $ npm installer --save auth0-js @ dernière firebase @ dernière angularfire2 @ dernière
    

    Nous aurons besoin de la bibliothèque auth0-js pour implémenter l'authentification Auth0 dans notre application Angular. Nous aurons aussi besoin de la bibliothèque firebase JS SDK et angularfire2 Angular Firebase pour implémenter nos commentaires en temps réel avec Firebase.

    Ajouter Bootstrap CSS

    Pour simplifier le style, nous allons ajouter le lien Bootstrap CSS CDN au de notre fichier index.html comme suit:

    
     ...
    
      ...
       Les 10 meilleurs chiens 
      ...
      
    
     ...
    

    Servir l'application Angular

    Vous pouvez servir l'application Angular avec la commande suivante:

     $ ng serve
    

    L'application tournera dans le navigateur à http: // localhost: 4200 .

    Angular App Architecture

    Nous allons utiliser l'interface CLI angulaire pour générer l'architecture complète de notre application à l'avant. De cette façon, nous pouvons nous assurer que nos modules fonctionnent correctement avant d'implémenter notre logique et nos templates.

    Notre application va utiliser une approche modulaire avec chargement paresseux . L'exemple d'application dans ce didacticiel est petit, mais nous souhaitons le construire de façon évolutive et réelle .

    Root Module

    Le module racine a déjà été créé lorsque l'application Angular a été créée. généré avec la commande ng new . Le module racine réside à src / app / app.module.ts . Tous les composants que nous générons dans notre application Angular sans le sous-répertoire d'un autre module spécifié seront automatiquement importés et déclarés dans notre module racine.

    Générons maintenant un composant avec la CLI:

     # create CallbackComponent:
    $ ng g callback de composant --is --it --flat --no-spec
    

    Cette commande est composée des éléments suivants:

    • ng g composant : génère un fichier de composant callback avec:
    • - is inline styles
    • - il modèle en ligne
    • - plat sans dossier contenant
    • - no-spec no .spec fichier de test

    Nous utiliserons le composant callback pour gérer la redirection après que l'utilisateur se connecte à notre application. C'est un composant très simple

    Note: g est un raccourci pour générer . Nous pourrions aussi utiliser c comme raccourci pour le composant en faisant cette commande ng g c . Cependant, ce tutoriel n'utilisera pas de raccourcis pour le type de fichiers générés, dans un souci de clarté

    Core Module Architecture

    Ensuite, nous allons créer le CoreModule et ses composants et services. C'est un module partagé . À partir de la racine de votre dossier de projet Angular, exécutez les commandes CLI suivantes. Assurez-vous d'exécuter la commande ng g core en premier comme suit:

     # create Module de base:
    $ ng g noyau de module
    # créer un service d'API sans fichier .spec:
    $ ng g noyau de service / api --no-spec
    # crée HeaderComponent avec des styles inline, pas de fichier .spec, et exporte dans le module:
    $ ng g core / header du composant --is --no-spec --export = true
    # create LoadingComponent avec les styles inline, le template inline, pas de dossier, pas de fichier .spec, et l'export dans le module:
    $ ng g core / chargement --is --it --flat --no-spec --export = true
    # create ErrorComponent avec les styles en ligne, le modèle en ligne, aucun dossier, aucun fichier .spec et l'exportation en module:
    $ ng g core / erreur de composant --is --it --flat --no-spec --export = true
    # créer une interface de type chien:
    $ ng g noyau d'interface / chien
    # créer une interface de type DogDetail:
    $ ng g noyau d'interface / chien-détail
    

    La ​​création du module garantit que les composants créés dans le dossier de ce module seront importés et déclarés automatiquement dans ce module parent au lieu du module racine de l'application.

    Remarque: Si vous souhaitez utiliser les composants d'un module partagé Dans un autre module, vous devez exporter les composants et les déclarer. Nous pouvons le faire automatiquement avec la CLI en utilisant le drapeau --export = true

    Voici l'architecture de base pour les services de base partagés, les composants et les modèles auxquels notre application aura besoin d'accéder

    Auth Module Architecture

    Ensuite, nous allons créer notre AuthModule . Exécutez les commandes CLI suivantes (encore une fois, assurez-vous de générer le module en premier):

     # create Module Auth:
    $ ng g module auth
    # crée AuthService sans fichier .spec:
    $ ng g service auth / auth --no-spec
    # crée un chemin d'accès Auth sans fichier .spec:
    $ ng g de garde auth / auth --no-spec
    

    Notre module Auth fournit le service et la protection d'itinéraire dont nous avons besoin pour gérer l'authentification, mais n'a aucun composant. C'est aussi un module partagé

    Dogs Module Architecture

    Notre page d'accueil sera fournie par le DogsModule . Ce sera la liste des dix chiens les plus populaires en 2016, classés par l'AKC. Utilisez les commandes CLI suivantes pour générer la structure de ce module de page chargé par défaut:

     # create Dogs module:
    $ ng g module chiens
    # create DogsComponent avec des styles en ligne et pas de fichier .spec:
    $ ng g composants chiens / chiens --is --no-spec
    

    Dog Module Architecture

    Notre application contiendra également des pages détaillées pour chaque chien listé dans le composant Chiens afin que les utilisateurs puissent en apprendre plus sur chaque race. Utilisez les commandes CLI suivantes pour générer la structure du module DogModule DogModule :

     # paresseux:
    $ ng g module chien
    # create DogComponent avec des styles inline et aucun fichier .spec:
    $ ng g composant chien / chien --is --no-spec
    

    Enfin, nous devons implémenter l'architecture nécessaire pour nos commentaires en temps réel Firebase. Utilisez les commandes CLI suivantes pour générer la structure pour le module CommentsModule :

     # create Comments:
    $ ng g commentaires du module
    # create classe de modèle de commentaire:
    $ ng g classe commentaires / commentaires
    # create CommentsComponent sans fichier .spec:
    $ ng g commentaires / commentaires sur les composants --no-spec --export = true
    # create CommentFormComponent avec des styles inline et aucun fichier .spec:
    $ ng g commentaires / commentaires / commentaire -formulaire --is --no-spec
    

    Configuration de l'environnement

    Ajoutons nos informations de configuration pour Auth0 et Firebase à notre front angulaire. Ouvrez le fichier environment.ts et ajoutez:

     // src / environments / environment.ts
    const FB_PROJECT_ID = '';
    
    export const environnement = {
      production: faux,
      auth: {
        clientId: '',
        clientDomain: '', // par exemple, you.auth0.com
        public: '', // par exemple, http: // localhost: 1337 /
        redirection: 'http: // localhost: 4200 / callback',
        scope: 'email de profil openid'
      },
      Firebase: {
        apiKey: '',
        authDomain: `$ {FB_PROJECT_ID} .firebaseapp.com`,
        databaseURL: `https: // $ {FB_PROJECT_ID} .firebaseio.com`,
        ID de projet: FB_PROJECT_ID,
        storageBucket: `$ {FB_PROJECT_ID} .appspot.com`,
        messagingSenderId: ''
      },
      apiRoot: '' // par exemple, http: // localhost: 1337 / (DO inclut une barre oblique)
    }
    

    Remplacez les espaces réservés dans par vos informations Auth0, Firebase et API appropriées

    Vous pouvez trouver votre configuration Auth0 dans votre Auth0 Dashboard dans les paramètres du client et de l'API que vous avez créés

    Vous pouvez trouver votre configuration Firebase dans la vue d'ensemble du projet de console Firebase après avoir cliqué sur la grande icône Ajouter Firebase à votre application web comme indiqué ci-dessous:

     Ajouter Firebase à votre application Web

    Ajouter Chargement Image

    La dernière chose que nous allons faire avant de commencer à implémenter des fonctionnalités dans notre application Angular est d'ajouter une image de chargement. Créez le dossier suivant: src / assets / images .

    Puis enregistrez en chargeant l'image SVG dans ce dossier:

     Chargement SVG

    Implémenter les modules partagés

    Mettons en place nos modules. Nous allons importer les modules partagés ( CoreModule et AuthModule ) dans notre racine AppModule .

    Module de base

    Nous allons d'abord implémenter notre CoreModule . Ouvrez le fichier core.module.ts et mettez à jour le code suivant:

     // src / app / core / core.module.ts
    importer {NgModule, ModuleWithProviders} à partir de '@ angular / core';
    importer {CommonModule} à partir de '@ angular / common';
    importer {HttpClientModule} à partir de '@ angular / common / http';
    importer {FormsModule} à partir de '@ angular / forms';
    importer {RouterModule} à partir de '@ angular / router';
    importer {Title} à partir de '@ angular / platform-browser';
    importer {DatePipe} à partir de '@ angular / common';
    importer {HeaderComponent} à partir de './header/header.component';
    importer {ApiService} à partir de './api.service';
    importer {LoadingComponent} à partir de './loading.component';
    importer {ErrorComponent} à partir de './error.component';
    
    @NgModule ({
      importations: [
        CommonModule,
        RouterModule,
        HttpClientModule, // AuthModule is a sibling and can use this without us exporting it
        FormsModule
      ],
      déclarations: [
        HeaderComponent,
        LoadingComponent,
        ErrorComponent
      ],
      exportations: [
        FormsModule, // Export FormsModule so CommentsModule can use it
        HeaderComponent,
        LoadingComponent,
        ErrorComponent
      ]
    })
    classe d'exportation CoreModule {
      static forRoot (): ModuleWithProviders {
        revenir {
          ngModule: CoreModule,
          fournisseurs: [
            Title,
            DatePipe,
            ApiService
          ]
        }
      }
    }
    

    Comme il s'agit d'un module partagé, nous allons importer les autres modules, services et composants auxquels nous aurons besoin tout au long de notre application.

    Note: ] CommonModule est importé dans tous les modules qui sont et non le module racine.

    Dans notre tableau imports nous ajoutons tous les modules qui peuvent être requis par les services ou composants dans le CoreModule ou qui doivent être disponibles pour autres modules dans notre application. L'interface de ligne de commande doit avoir ajouté automatiquement tous les composants générés au tableau déclarations . Le tableau exports doit contenir tous les modules ou composants que nous voulons mettre à la disposition des autres modules.

    Notez que nous avons importé ModuleWithProviders de @ angular / core À l'aide de ce module, nous pouvons créer une méthode forRoot () qui peut être appelée à l'importation dans la racine app.module.ts lorsque CoreModule est importé. De cette façon, nous pouvons nous assurer que tous les services que nous ajoutons à un tableau providers renvoyé par la méthode forRoot () restent singletons dans notre application. De cette manière, nous pouvons éviter plusieurs instances involontaires si d'autres modules de notre application doivent également importer le CoreModule .

    Auth Module

    Ensuite, ajoutons du code à notre AuthModule dans le fichier auth.module.ts :

     // src / app / auth / auth.module.ts
    importer {NgModule, ModuleWithProviders} à partir de '@ angular / core';
    importer {CommonModule} à partir de '@ angular / common';
    importer {AuthService} à partir de './auth.service';
    importer {AuthGuard} à partir de './auth.guard';
    importer {AngularFireAuthModule} à partir de 'angularfire2 / auth';
    
    @NgModule ({
      importations: [
        CommonModule,
        AngularFireAuthModule
      ]
    })
    classe d'exportation AuthModule {
      static forRoot (): ModuleWithProviders {
        revenir {
          ngModule: AuthModule,
          fournisseurs: [
            AuthService,
            AuthGuard
          ]
        }
      }
    }
    

    We’ll import ModuleWithProviders to implement a forRoot() method like we did with our CoreModule. Then we’ll import our AuthService and AuthGuard. We also need to import AngularFireAuthModule from angularfire2/auth so we can secure our Firebase connections in our AuthService. The service and guard should then be returned in the providers array in the forRoot() method.

    Open the comments.module.ts file to implement the CommentsModule like so:

    // src/app/comments/comments.module.ts
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { CoreModule } from '../core/core.module';
    import { environment } from './../../environments/environment';
    import { AngularFireModule } from 'angularfire2';
    import { AngularFirestoreModule } from 'angularfire2/firestore';
    import { CommentsComponent } from './comments/comments.component';
    import { CommentFormComponent } from './comments/comment-form/comment-form.component';
    
    @NgModule({
      imports: [
        CommonModule,
        CoreModule, // Access FormsModule, Loading, and Error components
        AngularFireModule.initializeApp(environment.firebase),
        AngularFirestoreModule
      ],
      declarations: [
        CommentsComponent,
        CommentFormComponent
      ],
      exports: [
        CommentsComponent
      ]
    })
    export class CommentsModule { }
    

    We’ll need to import the CoreModule so we can utilize its exported FormsModuleLoadingComponentand ErrorComponent. We also need to access our configuration from the environment.ts file. Comments use Firebase’s Cloud Firestore database, so let’s import the AngularFireModule and AngularFirestoreModule as well as our two components: CommentsComponent and CommentFormComponent.

    When we add AngularFireModule to the @NgModule’s imports array, we’ll call its initializeApp() method, passing in our Firebase configuration. Both of our components should already be in the declarations array, and the CommentsComponent should already be added to the exports array so that other components from other modules can use it.

    Note: We don’t need to export CommentsFormComponent because it’s a child of CommentsComponent.

    The CommentsModule does not provide any services, so there’s no need to implement a forRoot() method.

    App Module

    Now that our CoreModuleAuthModuleand CommentsModule have been implemented, we need to import them in our root module, the AppModule located in the app.module.ts file:

    // src/app/app.module.ts
    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { AppRoutingModule } from './app-routing.module';
    import { CoreModule } from './core/core.module';
    import { AuthModule } from './auth/auth.module';
    import { CommentsModule } from './comments/comments.module';
    import { AppComponent } from './app.component';
    import { CallbackComponent } from './callback.component';
    
    @NgModule({
      declarations: [
        AppComponent,
        CallbackComponent
      ],
      imports: [
        BrowserModule,
        AppRoutingModule,
        CoreModule.forRoot(),
        AuthModule.forRoot(),
        CommentsModule
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    

    The AppComponent and CallbackComponent have already been added automatically by the CLI. When we add our CoreModule and AuthModule to the imports array, we’ll call the forRoot() method to ensure no extra instances are created for their services. The CommentsModule doesn’t provide any services, so this is not a concern for that module.

    Implement Routing and Lazy Loaded Modules

    We have two modules that require routing: the DogsModule for the main listing of dogs, and the DogModulewhich contains the component showing a dog breed’s detail page.

    App Routing

    First let’s implement our app’s routing. Open the app-routing.module.ts file and add this code:

    // src/app/app-routing.module.ts
    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    import { CallbackComponent } from './callback.component';
    import { AuthGuard } from './auth/auth.guard';
    
    const routes: Routes = [
      {
        path: '',
        loadChildren: './dogs/dogs.module#DogsModule',
        pathMatch: 'full'
      },
      {
        path: 'dog',
        loadChildren: './dog/dog.module#DogModule',
        canActivate: [
          AuthGuard
        ]
      },
      {
        path: 'callback',
        component: CallbackComponent
      }
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
    

    We’ll import our CallbackComponent and AuthGuard. The remaining routes will be string references to modules rather than imported components using the loadChildren property.

    We will set the default '' path to load route children from the DogsModuleand the 'dog' path to load route children from the DogModule. The 'dog' path should also be protected by the AuthGuardwhich we declare using the canActivate property. This can hold an array of route guards should we require more than one. Finally, the 'callback' route should simply point to the CallbackComponent.

    Dogs Module

    Let’s add some code to the dogs.module.ts file:

    // src/app/dogs/dogs.module.ts
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { Routes, RouterModule } from '@angular/router';
    import { CoreModule } from '../core/core.module';
    import { CommentsModule } from '../comments/comments.module';
    import { DogsComponent } from './dogs/dogs.component';
    
    const DOGS_ROUTES: Routes = [
      {
        path: '',
        component: DogsComponent
      }
    ];
    
    @NgModule({
      imports: [
        CommonModule,
        CoreModule,
        RouterModule.forChild(DOGS_ROUTES),
        CommentsModule
      ],
      declarations: [
        DogsComponent
      ]
    })
    export class DogsModule { }
    

    We’ll import Routes and RouterModule in addition to our CoreModule and CommentsModule (comments will appear on the main dogs listing page).

    This module has a child route, so we’ll create a constant that contains an array to hold our route object. The only child route we’ll need inherits the '' path from app-routing.module.tsso its path should also be ''. It will load the DogsComponent. In our imports array, we’ll pass our DOGS_ROUTES constant to the RouterModule‘s forChild() method.

    Dog Module

    The DogModule works similarly to the DogsModule above. Open dog.module.ts and add the following:

    // src/app/dog/dog.module.ts
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { Routes, RouterModule } from '@angular/router';
    import { CoreModule } from '../core/core.module';
    import { DogComponent } from './dog/dog.component';
    
    const DOG_ROUTES: Routes = [
      {
        path: ':rank',
        component: DogComponent
      }
    ];
    
    @NgModule({
      imports: [
        CommonModule,
        CoreModule,
        RouterModule.forChild(DOG_ROUTES)
      ],
      declarations: [
        DogComponent
      ]
    })
    export class DogModule { }
    

    One difference between this module and the DogsModule is that our DOG_ROUTES has a path of :rank. This way, the route for any specific dog’s details is passed as a URL segment matching the dog’s rank in our list of top ten dog breeds, like so:

    http://localhost:4200/dog/3
    

    Another difference is that we will not import the CommentsModule. However, we could add comments to dog details in the future if we wished.

    Our app’s architecture and routing are now complete! The app should successfully compile and display in the browser, with lazy loading functioning properly to load shared code and the code for the specific route requested.

    We’re now ready to implement our application’s logic.

    Loading and Error Components

    The loading and error components are basic, core UI elements that can be used in many different places in our app. Let’s set them up now.

    Loading Component

    The LoadingComponent should simply show a loading image. (Recall that we already saved one when we set up the architecture of our app.) However, it should be capable of displaying the image large and centered, or small and inline.

    Open the loading.component.ts file and add:

    // src/app/core/loading.component.ts
    import { Component, Input } from '@angular/core';
    
    @Component({
      selector: 'app-loading',
      template: `
        
    `,   styles: [` .inline { display: inline-block; } img { height: 80px; width: 80px; } .inline img { height: 24px; width: 24px; } `] }) export class LoadingComponent {   @Input() inline: boolean; }

    Using the @Input() decoratorwe can pass information into the component from its parent, telling it whether we should display the component inline or not. We’ll use the NgClass directive ([ngClass]) in our template to conditionally add the appropriate styles for the display we want. Displaying this component in another template will look like this:

    
    
    
    
    

    Error Component

    Next let’s quickly implement our ErrorComponent. This component will display a simple error message if shown. Open the error.component.ts file and add:

    // src/app/core/error.component.ts
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-error',
      template: `
        

    Error: There was an error retrieving data.     

      ` }) export class ErrorComponent { }

    Authentication Logic

    Now let’s implement the code necessary to get our AuthModule‘s features working. We’ll need the authentication service in order to build out the header in the CoreModuleso it makes sense to start here. We’ve already installed the necessary dependencies (Auth0 and FirebaseAuth), so let’s begin.

    Authentication Service

    Before we write any code, we’ll determine what the requirements are for this service. We need to:

    • Create a login() method that will allow users to authenticate using Auth0
    • If user was prompted to log in by attempting to access a protected route, make sure they can be redirected to that route after successful authentication
    • Get the user’s profile information and set up their session
    • Establish a way for the app to know whether the user is logged in or not
    • Request a Firebase custom token from the API with authorization from the Auth0 access token
    • If successful in acquiring a Firebase token, sign into Firebase using the returned token and establish a way for the app to know whether the user is logged into Firebase or not
    • Custom tokens minted by Firebase expire after an hour, so we should set up a way to automatically renew tokens that expire
    • Create a logout() method to clear session and sign out of Firebase.

    Open the auth.service.ts file that we generated earlier.

    For tutorial brevity, please check out the full code in the GitHub repo’s auth.service.ts file here.

    There’s a lot going on, so let’s go through it step by step.

    First, as always, we’ll import our dependencies. This includes our environment configuration we set up earlier to provide our Auth0, Firebase, and API settings, as well as auth0 and firebase libraries, AngularFireAuthHttpClient to call the API to get a custom Firebase token, and the necessary RxJS imports.

    You can refer to the code comments for descriptions of the private and public members of our AuthService class.

    Next is our constructor function, where we’ll make RouterAngularFireAuthand HttpClient available for use in our class.

    The login() method looks like this:

    login(redirect?: string) {
      // Set redirect after login
      const _redirect = redirect ? redirect : this.router.url;
      localStorage.setItem('auth_redirect', _redirect);
      // Auth0 authorize request
      this._auth0.authorize();
    }
    

    If a redirect URL segment is passed into the method, we’ll save it in local storage. If no redirect is passed, we’ll simply store the current URL. We’ll then use the _auth0 instance we created in our members and call Auth0’s authorize() method to go to the Auth0 login page so our user can authenticate.

    The next three methods are handleLoginCallback()getUserInfo()and _setSession():

    handleLoginCallback() {
      this.loading = true;
      // When Auth0 hash parsed, get profile
      this._auth0.parseHash((err, authResult) => {
        if (authResult && authResult.accessToken) {
          window.location.hash = '';
          // Store access token
          this.accessToken = authResult.accessToken;
          // Get user info: set up session, get Firebase token
          this.getUserInfo(authResult);
        } else if (err) {
          this.router.navigate(['/']);
          this.loading = false;
          console.error(`Error authenticating: ${err.error}`);
        }
      });
    }
    
    getUserInfo(authResult) {
      // Use access token to retrieve user's profile and set session
      this._auth0.client.userInfo(this.accessToken, (err, profile) => {
        if (profile) {
          this._setSession(authResult, profile);
        } else if (err) {
          console.warn(`Error retrieving profile: ${err.error}`);
        }
      });
    }
    
    private _setSession(authResult, profile) {
      // Set tokens and expiration in localStorage
      const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + Date.now());
      localStorage.setItem('expires_at', expiresAt);
      this.userProfile = profile;
      // Session set; set loggedIn and loading
      this.loggedIn = true;
      this.loading = false;
      // Get Firebase token
      this._getFirebaseToken();
      // Redirect to desired route
      this.router.navigateByUrl(localStorage.getItem('auth_redirect'));
    

    These methods are fairly self-explanatory: they use Auth0 methods parseHash() and userInfo() to extract authentication results and get the user’s profile. We’ll also set our service’s properties to store necessary state (such as whether the user’s authentication state is loading and if they’re logged in or not), handle errors, save data to our service and local storage, and redirect to the appropriate route.

    We are also going to use the authentication result’s access token to authorize an HTTP request to our API to get a Firebase token. This is done with the _getFirebaseToken() and _firebaseAuth() methods:

      private _getFirebaseToken() {
        // Prompt for login if no access token
        if (!this.accessToken) {
          this.login();
        }
        const getToken$ = () => {
          return this.http
            .get(`${environment.apiRoot}auth/firebase`, {
              headers: new HttpHeaders().set('Authorization', `Bearer ${this.accessToken}`)
            });
        };
        this.firebaseSub = getToken$().subscribe(
          res => this._firebaseAuth(res),
          err => console.error(`An error occurred fetching Firebase token: ${err.message}`)
        );
      }
    
      private _firebaseAuth(tokenObj) {
        this.afAuth.auth.signInWithCustomToken(tokenObj.firebaseToken)
          .then(res => {
            this.loggedInFirebase = true;
            // Schedule token renewal
            this.scheduleFirebaseRenewal();
            console.log('Successfully authenticated with Firebase!');
          })
          .catch(err => {
            const errorCode = err.code;
            const errorMessage = err.message;
            console.error(`${errorCode} Could not log into Firebase: ${errorMessage}`);
            this.loggedInFirebase = false;
          });
      }
    

    We’ll create a getToken$ observable from the GET request to our API’s /auth/firebase endpoint and subscribe to it. If successful, we’ll pass the returned object with the custom Firebase token to the _firebaseAuth() method, which will authenticate with Firebase using Firebase’s signInWithCustomToken() method. This method returns a promise, and when the promise is resolved, we can tell our app that Firebase login was successful. We can also schedule Firebase token renewal (we’ll look at this shortly). We’ll handle any errors appropriately.

    Our custom Firebase token will expire in 3600 seconds (1 hour). This is only half as long as our default Auth0 access token lifetime (which is 7200 seconds, or 2 hours). To avoid having our users lose access to Firebase unexpectedly in the middle of a session, we’ll set up automatic Firebase token renewal with two methods: scheduleFirebaseRenewal() and unscheduleFirebaseRenewal().

    Note: You can also implement automatic session renewal with Auth0 in a similar manner using the checkSession() method. In addition, you could use checkSession() to restore an unexpired authentication session in the constructor if a user navigates away from the app and then returns later. We won’t cover that in this tutorial, but this is something you should try on your own!

    scheduleFirebaseRenewal() {
      // If user isn't authenticated, check for Firebase subscription
      // and unsubscribe, then return (don't schedule renewal)
      if (!this.loggedInFirebase) {
        if (this.firebaseSub) {
          this.firebaseSub.unsubscribe();
        }
        return;
      }
      // Unsubscribe from previous expiration observable
      this.unscheduleFirebaseRenewal();
      // Create and subscribe to expiration observable
      // Custom Firebase tokens minted by Firebase
      // expire after 3600 seconds (1 hour)
      const expiresAt = new Date().getTime() + (3600 * 1000);
      const expiresIn$ = Observable.of(expiresAt)
        .pipe(
          mergeMap(
            expires => {
              const now = Date.now();
              // Use timer to track delay until expiration
              // to run the refresh at the proper time
              return Observable.timer(Math.max(1, expires - now));
            }
          )
        );
    
      this.refreshFirebaseSub = expiresIn$
        .subscribe(
          () => {
            console.log('Firebase token expired; fetching a new one');
            this._getFirebaseToken();
          }
        );
    }
    
    unscheduleFirebaseRenewal() {
      if (this.refreshFirebaseSub) {
        this.refreshFirebaseSub.unsubscribe();
      }
    }
    

    To schedule automatic token renewal, we’ll create a timer observable that counts down to the token’s expiration time. We can subscribe to the expiresIn$ observable and then call our _getFirebaseToken() method again to acquire a new token. The signInWithCustomToken() angularfire2 auth method returns a promise. When the promise resolves, scheduleFirebaseRenewal() is called, which in turn ensures that the token will continue to be renewed as long as the user is logged into our app.

    We’ll also need to be able to unsubscribe from token renewal, so we’ll create a method for that as well.

    Finally, the last two methods in our authentication service are logout() and tokenValid():

    logout() {
      // Ensure all auth items removed
      localStorage.removeItem('expires_at');
      localStorage.removeItem('auth_redirect');
      this.accessToken = undefined;
      this.userProfile = undefined;
      this.loggedIn = false;
      // Sign out of Firebase
      this.loggedInFirebase = false;
      this.afAuth.auth.signOut();
      // Return to homepage
      this.router.navigate(['/']);
    }
    
    get tokenValid(): boolean {
      // Check if current time is past access token's expiration
      const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
      return Date.now() < expiresAt;
    }
    

    The logout() method removes all session information from local storage and from our service, signs out of Firebase Auth, and redirects the user back to the homepage (the only public route in our app).

    The tokenValid accessor method checks whether the Auth0 access token is expired or not by comparing its expiration to the current datetime. This can be useful for determining if the user needs a new access token; we won’t cover that in this tutorial, but you may want to explore Auth0 session renewal further on your own.

    That’s it for our AuthService!

    Callback Component

    Recall that we created a CallbackComponent in our root module. In addition, we set our environment‘s Auth0 redirect to the callback component’s route. That means that when the user logs in with Auth0, they will return to our app at the /callback route with the authentication hash appended to the URI.

    We created our AuthService with methods to handle authentication and set sessions, but currently these methods aren’t being called from anywhere. The callback component is the appropriate place for this code to execute.

    Open the callback.component.ts file and add:

    // src/app/callback.component.ts
    import { Component, OnInit } from '@angular/core';
    import { AuthService } from './auth/auth.service';
    
    @Component({
      selector: 'app-callback',
      template: `
        
      `
    })
    export class CallbackComponent implements OnInit {
    
      constructor(private auth: AuthService) { }
    
      ngOnInit() {
        this.auth.handleLoginCallback();
      }
    
    }
    

    All our callback component needs to do is show the LoadingComponent while the AuthService‘s handleAuth() method executes. The handleLoginCallback() method will parse the authentication hash, get the user’s profile info, set their session, and redirect to the appropriate route in the app.

    Auth Guard

    Now that we’ve implemented the authentication service, we have access to the properties and methods necessary to effectively use authentication state throughout our Angular application. Let’s use this logic to implement our AuthGuard for protecting routes.

    Using the Angular CLI should have generated some helpful boilerplate code, and we only have to make a few minor changes to ensure that our guarded routes are only accessible to authenticated users.

    Note: It’s important to note that route guards on their own do not confer sufficient security. You should always secure your API endpoints, as we have done in this tutorial, and never rely solely on the client side to authorize access to protected data.

    Open the auth.guard.ts file and make the following changes:

    // src/app/auth/auth.guard.ts
    import { Injectable } from '@angular/core';
    import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
    import { Observable } from 'rxjs/Observable';
    import { AuthService } from './auth.service';
    
    @Injectable()
    export class AuthGuard implements CanActivate {
    
      constructor(private auth: AuthService) { }
    
      canActivate(
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): Observable | Promise | boolean {
        if (this.auth.loggedIn) {
          return true;
        } else {
          // Send guarded route to redirect after logging in
          this.auth.login(state.url);
          return false;
        }
      }
    }
    

    We’ll import AuthService add a constructor() function to make the service available in our route guard. The canActivate() method should return true if conditions are met to grant access to a route, and false if not. In our case, the user should be able to access the guarded route if they are authenticated. The loggedIn property from our AuthService provides this information.

    If the user does not have a valid token, we’ll prompt them to log in. We want them to be redirected back to the guarded route after they authenticate, so we’ll call the login() method and pass the guarded route (state.url) as the redirect parameter.

    Note: Remember that we set up our entire app’s architecture and routing earlier. We already added AuthGuard to our dog details route, so it should be protected now that we’ve implemented the guard.

    Core Logic

    The last thing we’ll do in this section of our tutorial is build out the remaining components and services that belong to our CoreModule. We’ve already taken care of the LoadingComponent and ErrorComponentso let’s move on to the header.

    The header will use methods and logic from our authentication service to show login and logout buttons as well as display the user’s name and picture if they’re authenticated. Open the header.component.ts file and add:

    // src/app/core/header/header.component.ts
    import { Component } from '@angular/core';
    import { AuthService } from '../../auth/auth.service';
    
    @Component({
      selector: 'app-header',
      templateUrl: './header.component.html',
      styles: [`
        img {
          border-radius: 100px;
          width: 30px;
        }
        .loading { line-height: 31px; }
        .home-link { color: #212529; }
        .home-link:hover { text-decoration: none; }
      `]
    })
    export class HeaderComponent {
    
      constructor(public auth: AuthService) {}
    
    }
    

    We’ll add a few simple styles and import our AuthService to make its members publicly available to our header component’s template.

    Next open the header.component.html file and add:

    
    
    

    The header now shows:

    • The name of our app (“Popular Dogs”) with a link to the / route
    • A login button if the user is not authenticated
    • A “Logging in…” message if the user is currently authenticating
    • The user’s picture, name, and a logout button if the user is authenticated

    Now that we have our header component built, we need to display it in our app.

    Open the app.component.html file and add:

    
    
    
      

    The header component will now be displayed in our app with the current routed component showing beneath it. Check it out in the browser and try logging in!

    Dog and DogDetail Models

    Let’s implement our dog.ts and dog-detail.ts interfaces. These are models that specify types for the shape of values that we’ll use in our app. Using models ensures that our data has the structure that we expect.

    We’ll start with the dog.ts interface:

    // src/app/core/dog.ts
    export interface Dog {
      breed: string;
      rank: number;
      image: string;
    }
    

    Next let’s implement the dog-detail.ts interface:

    // src/app/core/dog-detail.ts
    export interface DogDetail {
      breed: string;
      rank: number;
      description: string;
      personality: string;
      energy: string;
      group: string;
      image: string;
      link: string;
    }
    

    API Service

    With our Node API and models in place, we’re ready to implement the service that will call our API in the Angular front end.

    Open the api.service.ts file and add this code:

    // src/app/core/api.service.ts
    import { Injectable } from '@angular/core';
    import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
    import { environment } from './../../environments/environment';
    import { AuthService } from './../auth/auth.service';
    import { Observable } from 'rxjs/Observable';
    import { catchError } from 'rxjs/operators';
    import 'rxjs/add/observable/throw';
    import { Dog } from './../core/dog';
    import { DogDetail } from './../core/dog-detail';
    
    @Injectable()
    export class ApiService {
      private _API = `${environment.apiRoot}api`;
    
      constructor(
        private http: HttpClient,
        private auth: AuthService) { }
    
      getDogs$(): Observable {
        return this.http
          .get(`${this._API}/dogs`)
          .pipe(
            catchError((err, caught) => this._onError(err, caught))
          );
      }
    
      getDogByRank$(rank: number): Observable {
        return this.http
          .get(`${this._API}/dog/${rank}`, {
            headers: new HttpHeaders().set('Authorization', `Bearer ${this.auth.accessToken}`)
          })
          .pipe(
            catchError((err, caught) => this._onError(err, caught))
          );
      }
    
      private _onError(err, caught) {
        let errorMsg = 'Error: Unable to complete request.';
        if (err instanceof HttpErrorResponse) {
          errorMsg = err.message;
          if (err.status === 401 || errorMsg.indexOf('No JWT') > -1 || errorMsg.indexOf('Unauthorized') > -1) {
            this.auth.login();
          }
        }
        return Observable.throw(errorMsg);
      }
    
    }
    

    We’ll add the necessary imports to handle HTTP in Angular along with the environment configuration, AuthServiceRxJS imports, and Dog and DogDetail models we just created. We’ll set up private members for the _API and to store the _accessTokenthen make the HttpClient and AuthService available privately to our API service.

    Our API methods will return observables that emit one value when the API is either called successfully or an error is thrown. The getDogs$() stream returns an observable with an array of objects that are Dog-shaped. The getDogByRank$(rank) stream requires a numeric rank to be passed in, and will then call the API to retrieve the requested Dog‘s data. This API call will send an Authorization header containing the authenticated user’s access token.

    Finally, we’ll create an error handler that checks for errors and assesses if the user is not authenticated and prompts for login if so. The observable will then terminate with an error.

    Note: We are using arrow functions to pass parameters to our handler functions for RxJS pipeable operators (such as catchError). This is done to preserve the scope of the this keyword (see the “No separate this” section of the MDN arrow functions documentation).

    Next Steps

    We’ve already accomplished a lot in the first part of our tutorial series. In the next part, we’ll finish our Popular Dogs application. In the meantime, here are some additional resources that you may want to check out:

    Angular Testing Resources

    If you’re interested in learning more about testing in Angular, which we did not cover in this tutorial, please check out some of the following resources:

    Additional Resources

    You can find more resources on Firebase, Auth0, and Angular here:

    In the next installment of our Auth0 + Firebase + Angular tutorial, we’ll display data from our dogs API and learn how to set up and implement realtime comments with Firebase! Check out Authenticating Firebase and Angular with Auth0: Part 2 now.




Source link