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:- Firebase et Auth0
- Ce que nous allons construire
- Angular CLI
- Client Auth0 et API
- Projet Firebase avec compte de service
- API nœud
- Configuration de l'application angulaire
- Architecture de l'application angulaire
- Implémentation des modules partagés
- Implémentation des modules de routage et chargement par chargement
- Chargement et erreur des composants
- Logique d'authentification
- Logique du noyau
- 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:
- avez déjà implémenté Auth0 et souhaitez ajouter des fonctionnalités en temps réel à votre application
- Besoin d'utiliser facilement des jetons émis pour sécuriser un backend qui n'est pas fourni par Firebas
- Nécessité d'intégrer les fournisseurs d'identité sociale au-delà de Google, Facebook, Twitter et GitHub
- Besoin d'intégrer les fournisseurs d'identité d'entreprise tels que Active Directory, LDAP, ADFS , SAMLP, etc.
- Besoin d'un flux d'authentification personnalisé
- Besoin d'une gestion des utilisateurs robuste avec des API et un tableau de bord convivial
- pour pouvoir dynamiquement enrichir les profils d'utilisateurs
- Vous voulez des fonctionnalités comme personnalisable sans-mot de passe authentification multifactorielle violation de mot de passe détection d'anomalie etc.
- Doit adhérer à règlements de conformité tels que HIPAA, GDPR, SOC2, etc
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.
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 .
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
- Accédez à votre tableau de bord Auth0 et cliquez sur le bouton Créer un nouveau client .
- Nommez votre nouvelle application (quelque chose comme
Angular Firebase
) et sélectionnez Applications Web page unique . - Dans les paramètres pour votre nouvelle application client Auth0, ajoutez
http: // localhost : 4200 / callback
aux URL de rappel autorisées . - 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".
- 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
- 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 esthttp: // localhost: 1337 /
. L'algorithme de signature doit être "RS256". - 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
- Accédez à la Firebase Console et connectez-vous avec votre compte Google
- Cliquez sur Ajouter un projet .
- 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. - 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 . :
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 dossierfirebase
. Nous veillerons à ce que le dossier soit archivé, mais son contenu ne sera jamais envoyé à un repo en utilisant lefirebase / .gitignore
comme ceci:# firebase /. gitignore * * / ! .gitignore
Cette configuration
.gitignore
garantit que Git ignorera tous les fichiers et dossiers du répertoirefirebase
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
etexpress
pour servir nos points de terminaison API. L'authentification s'appuiera surexpress-jwt
etjwks-rsa
tandis que le jeton de jetons Firebase est implémenté avec le SDKfirebase-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èquefirebase
JS SDK etangularfire2
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 composantcallback
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 pourgénérer
. Nous pourrions aussi utiliserc
comme raccourci pour le composantng 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 commandeng 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éesVous 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 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:
Implémenter les modules partagés
Mettons en place nos modules. Nous allons importer les modules partagés (
CoreModule
etAuthModule
) dans notre racineAppModule
.Module de base
Nous allons d'abord implémenter notre
CoreModule
. Ouvrez le fichiercore.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 leCoreModule
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 tableaudéclarations
. Le tableauexports
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éthodeforRoot ()
qui peut être appelée à l'importation dans la racineapp.module.ts
lorsqueCoreModule
est importé. De cette façon, nous pouvons nous assurer que tous les services que nous ajoutons à un tableauproviders
renvoyé par la méthodeforRoot ()
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 leCoreModule
.Auth Module
Ensuite, ajoutons du code à notre AuthModule
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 aforRoot()
method like we did with ourCoreModule
. Then we’ll import ourAuthService
andAuthGuard
. We also need to importAngularFireAuthModule
fromangularfire2/auth
so we can secure our Firebase connections in ourAuthService
. The service and guard should then be returned in theproviders
array in theforRoot()
method.Open the
comments.module.ts
file to implement theCommentsModule
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 exportedFormsModule
LoadingComponent
andErrorComponent
. We also need to access our configuration from theenvironment.ts
file. Comments use Firebase’s Cloud Firestore database, so let’s import theAngularFireModule
andAngularFirestoreModule
as well as our two components:CommentsComponent
andCommentFormComponent
.When we add
AngularFireModule
to the @NgModule’simports
array, we’ll call itsinitializeApp()
method, passing in our Firebase configuration. Both of our components should already be in thedeclarations
array, and theCommentsComponent
should already be added to theexports
array so that other components from other modules can use it.Note: We don’t need to export
CommentsFormComponent
because it’s a child ofCommentsComponent
.The
CommentsModule
does not provide any services, so there’s no need to implement aforRoot()
method.App Module
Now that our
CoreModule
AuthModule
andCommentsModule
have been implemented, we need to import them in our root module, theAppModule
located in theapp.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
andCallbackComponent
have already been added automatically by the CLI. When we add ourCoreModule
andAuthModule
to theimports
array, we’ll call theforRoot()
method to ensure no extra instances are created for their services. TheCommentsModule
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 theDogModule
which 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
andAuthGuard
. The remaining routes will be string references to modules rather than imported components using theloadChildren
property.We will set the default
''
path to load route children from theDogsModule
and the'dog'
path to load route children from theDogModule
. The'dog'
path should also be protected by theAuthGuard
which we declare using thecanActivate
property. This can hold an array of route guards should we require more than one. Finally, the'callback'
route should simply point to theCallbackComponent
.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
andRouterModule
in addition to ourCoreModule
andCommentsModule
(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 fromapp-routing.module.ts
so its path should also be''
. It will load theDogsComponent
. In ourimports
array, we’ll pass ourDOGS_ROUTES
constant to theRouterModule
‘sforChild()
method.Dog Module
The
DogModule
works similarly to theDogsModule
above. Opendog.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 ourDOG_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: `
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 theerror.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 theCoreModule
so 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 asauth0
andfirebase
libraries,AngularFireAuth
HttpClient
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
Router
AngularFireAuth
andHttpClient
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’sauthorize()
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()
anduserInfo()
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 theGET
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’ssignInWithCustomToken()
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 is7200
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()
andunscheduleFirebaseRenewal()
.Note: You can also implement automatic session renewal with Auth0 in a similar manner using the
checkSession()
method. In addition, you could usecheckSession()
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. ThesignInWithCustomToken()
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()
andtokenValid()
: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 ourenvironment
‘s Auth0redirect
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 theAuthService
‘shandleAuth()
method executes. ThehandleLoginCallback()
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 aconstructor()
function to make the service available in our route guard. ThecanActivate()
method should returntrue
if conditions are met to grant access to a route, andfalse
if not. In our case, the user should be able to access the guarded route if they are authenticated. TheloggedIn
property from ourAuthService
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 theLoadingComponent
andErrorComponent
so 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
anddog-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,
AuthService
RxJS imports, andDog
andDogDetail
models we just created. We’ll set up private members for the_API
and to store the_accessToken
then make theHttpClient
andAuthService
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 areDog
-shaped. ThegetDogByRank$(rank)
stream requires a numeric rank to be passed in, and will then call the API to retrieve the requestedDog
‘s data. This API call will send anAuthorization
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 thethis
keyword (see the “No separatethis
” 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