Comment créer une API basée sur les rôles avec l'authentification Firebase

Publié: 2022-03-11

Dans ce didacticiel, nous allons créer une API REST pour gérer les utilisateurs et les rôles à l'aide de Firebase et Node.js. De plus, nous verrons comment utiliser l'API pour autoriser (ou non) quels utilisateurs peuvent accéder à des ressources spécifiques.

introduction

Presque toutes les applications nécessitent un certain niveau de système d'autorisation. Dans certains cas, la validation d'un ensemble nom d'utilisateur/mot de passe avec notre table Utilisateurs suffit, mais souvent, nous avons besoin d'un modèle d'autorisations plus fin pour permettre à certains utilisateurs d'accéder à certaines ressources et de les restreindre à d'autres. Construire un système pour supporter ce dernier n'est pas anodin et peut prendre beaucoup de temps. Dans ce didacticiel, nous allons apprendre à créer une API d'authentification basée sur les rôles à l'aide de Firebase, ce qui nous aidera à être rapidement opérationnel.

Authentification basée sur les rôles

Dans ce modèle d'autorisation, l'accès est accordé à des rôles, au lieu d'utilisateurs spécifiques, et un utilisateur peut en avoir un ou plusieurs selon la façon dont vous concevez votre modèle d'autorisation. Les ressources, en revanche, nécessitent certains rôles pour permettre à un utilisateur de l'exécuter.

Authentification basée sur les rôles illustrée

Base de feu

Authentification Firebase

En un mot, Firebase Authentication est un système d'authentification extensible basé sur des jetons et fournit des intégrations prêtes à l'emploi avec les fournisseurs les plus courants tels que Google, Facebook et Twitter, entre autres.

Cela nous permet d'utiliser des revendications personnalisées que nous utiliserons pour créer une API flexible basée sur les rôles.

Nous pouvons définir n'importe quelle valeur JSON dans les revendications (par exemple, { role: 'admin' } ou { role: 'manager' } ).

Une fois définies, les revendications personnalisées seront incluses dans le jeton généré par Firebase, et nous pouvons lire la valeur pour contrôler l'accès.

Il est également livré avec un quota gratuit très généreux, qui dans la plupart des cas sera plus que suffisant.

Fonctions Firebase

Les fonctions sont un service de plate-forme sans serveur entièrement géré. Nous avons juste besoin d'écrire notre code dans Node.js et de le déployer. Firebase s'occupe de la mise à l'échelle de l'infrastructure à la demande, de la configuration du serveur, etc. Dans notre cas, nous l'utiliserons pour créer notre API et l'exposer via HTTP sur le Web.

Firebase nous permet de définir des applications express.js comme gestionnaires pour différents chemins. Par exemple, vous pouvez créer une application Express et l'accrocher à /mypath , et toutes les requêtes arrivant sur cette route seront gérées par l' app configurée.

Dans le contexte d'une fonction, vous avez accès à l'ensemble de l'API Firebase Authentication, à l'aide du SDK Admin.

C'est ainsi que nous allons créer l'API utilisateur.

Ce que nous allons construire

Alors avant de commencer, jetons un coup d'œil à ce que nous allons construire. Nous allons créer une API REST avec les endpoints suivants :

Verbe HTTP Chemin La description Autorisation
AVOIR /utilisateurs Répertorie tous les utilisateurs Seuls les administrateurs et les gestionnaires ont accès
PUBLIER /utilisateurs Crée un nouvel utilisateur Seuls les administrateurs et les gestionnaires ont accès
AVOIR /utilisateurs/:identifiant Obtient l'utilisateur :id Les administrateurs, les gestionnaires et le même utilisateur que :id ont accès
PIÈCE /utilisateurs/:identifiant Met à jour l'utilisateur :id Les administrateurs, les gestionnaires et le même utilisateur que :id ont accès
EFFACER /utilisateurs/:identifiant Supprime l'utilisateur :id Les administrateurs, les gestionnaires et le même utilisateur que :id ont accès

Chacun de ces points de terminaison gérera l'authentification, validera l'autorisation, effectuera l'opération correspondante et renverra enfin un code HTTP significatif.

Nous allons créer les fonctions d'authentification et d'autorisation requises pour valider le jeton et vérifier si les revendications contiennent le rôle requis pour exécuter l'opération.

Construire l'API

Pour construire l'API, nous aurons besoin de :

  • Un projet Firebase
  • firebase-tools installés

Tout d'abord, connectez-vous à Firebase :

 firebase login

Ensuite, initialisez un projet Functions :

 firebase init ? Which Firebase CLI features do you want to set up for this folder? ... (O) Functions: Configure and deploy Cloud Functions ? Select a default Firebase project for this directory: {your-project} ? What language would you like to use to write Cloud Functions? TypeScript ? Do you want to use TSLint to catch probable bugs and enforce style? Yes ? Do you want to install dependencies with npm now? Yes

À ce stade, vous aurez un dossier Functions, avec une configuration minimale pour créer des fonctions Firebase.

Sur src/index.ts , il y a un exemple helloWorld , que vous pouvez décommenter pour valider que vos fonctions fonctionnent. Ensuite, vous pouvez cd functions et exécuter npm run serve . Cette commande transpilera le code et démarrera le serveur local.

Vous pouvez vérifier les résultats sur http://localhost:5000/{your-project}/us-central1/helloWorld

Une nouvelle application Firebase

Notez que la fonction est exposée sur le chemin défini comme son nom à 'index.ts: 'helloWorld' .

Créer une fonction HTTP Firebase

Codez maintenant notre API. Nous allons créer une fonction http Firebase et l'accrocher au chemin /api .

Tout d'abord, installez npm install express .

Sur le src/index.ts nous allons :

  • Initialisez le module SDK firebase-admin avec admin.initializeApp();
  • Définissez une application Express comme gestionnaire de notre point de terminaison api https
 import * as functions from 'firebase-functions'; import * as admin from 'firebase-admin'; import * as express from 'express'; admin.initializeApp(); const app = express(); export const api = functions.https.onRequest(app);

Désormais, toutes les requêtes adressées à /api seront traitées par l'instance de l' app .

La prochaine chose que nous allons faire est de configurer l'instance d' app pour prendre en charge CORS et ajouter le middleware d'analyseur de corps JSON. De cette façon, nous pouvons faire des requêtes à partir de n'importe quelle URL et analyser les requêtes au format JSON.

Nous allons d'abord installer les dépendances requises.

 npm install --save cors body-parser
 npm install --save-dev @types/cors

Et alors:

 //... import * as cors from 'cors'; import * as bodyParser from 'body-parser'; //... const app = express(); app.use(bodyParser.json()); app.use(cors({ origin: true })); export const api = functions.https.onRequest(app);

Enfin, nous configurerons les itinéraires que l' app gérera.

 //... import { routesConfig } from './users/routes-config'; //… app.use(cors({ origin: true })); routesConfig(app) export const api = functions.https.onRequest(app);

Firebase Functions nous permet de définir une application Express comme gestionnaire, et tout chemin après celui que vous avez configuré sur functions.https.onRequest(app); —dans ce cas, api —sera également géré par l' app . Cela nous permet d'écrire des points de terminaison spécifiques tels que api/users et de définir un gestionnaire pour chaque verbe HTTP, ce que nous ferons ensuite.

Créons le fichier src/users/routes-config.ts

Ici, nous allons définir un gestionnaire de create à POST '/users'

 import { Application } from "express"; import { create} from "./controller"; export function routesConfig(app: Application) { app.post('/users', create ); }

Maintenant, nous allons créer le fichier src/users/controller.ts .

Dans cette fonction, nous validons d'abord que tous les champs sont dans le corps de la requête, puis nous créons l'utilisateur et définissons les revendications personnalisées.

Nous transmettons simplement { role } dans setCustomUserClaims - les autres champs sont déjà définis par Firebase.

Si aucune erreur ne se produit, nous renvoyons un code 201 avec l' uid de l'utilisateur créé.

 import { Request, Response } from "express"; import * as admin from 'firebase-admin' export async function create(req: Request, res: Response) { try { const { displayName, password, email, role } = req.body if (!displayName || !password || !email || !role) { return res.status(400).send({ message: 'Missing fields' }) } const { uid } = await admin.auth().createUser({ displayName, password, email }) await admin.auth().setCustomUserClaims(uid, { role }) return res.status(201).send({ uid }) } catch (err) { return handleError(res, err) } } function handleError(res: Response, err: any) { return res.status(500).send({ message: `${err.code} - ${err.message}` }); }

Maintenant, sécurisons le gestionnaire en ajoutant une autorisation. Pour ce faire, nous allons ajouter quelques gestionnaires à notre point de terminaison de create . Avec express.js , vous pouvez définir une chaîne de gestionnaires qui seront exécutés dans l'ordre. Dans un gestionnaire, vous pouvez exécuter du code et le transmettre au gestionnaire next() ou renvoyer une réponse. Ce que nous allons faire, c'est d'abord authentifier l'utilisateur, puis valider s'il est autorisé à s'exécuter.

Dans le fichier src/users/routes-config.ts :

 //... import { isAuthenticated } from "../auth/authenticated"; import { isAuthorized } from "../auth/authorized"; export function routesConfig(app: Application) { app.post('/users', isAuthenticated, isAuthorized({ hasRole: ['admin', 'manager'] }), create ); }

Créons les fichiers src/auth/authenticated.ts .

Sur cette fonction, nous allons valider la présence du jeton du porteur d' authorization dans l'entête de la requête. Ensuite, nous le décoderons avec admin.auth().verifyidToken() et conserverons l' uid , le role et l'e- email de l'utilisateur dans la variable res.locals , que nous utiliserons plus tard pour valider l'autorisation.

Dans le cas où le jeton n'est pas valide, nous renvoyons une réponse 401 au client :

 import { Request, Response } from "express"; import * as admin from 'firebase-admin' export async function isAuthenticated(req: Request, res: Response, next: Function) { const { authorization } = req.headers if (!authorization) return res.status(401).send({ message: 'Unauthorized' }); if (!authorization.startsWith('Bearer')) return res.status(401).send({ message: 'Unauthorized' }); const split = authorization.split('Bearer ') if (split.length !== 2) return res.status(401).send({ message: 'Unauthorized' }); const token = split[1] try { const decodedToken: admin.auth.DecodedIdToken = await admin.auth().verifyIdToken(token); console.log("decodedToken", JSON.stringify(decodedToken)) res.locals = { ...res.locals, uid: decodedToken.uid, role: decodedToken.role, email: decodedToken.email } return next(); } catch (err) { console.error(`${err.code} - ${err.message}`) return res.status(401).send({ message: 'Unauthorized' }); } }

Maintenant, créons un fichier src/auth/authorized.ts .

Dans ce gestionnaire, nous extrayons les informations de l'utilisateur de res.locals que nous avons définis précédemment et validons s'il a le rôle requis pour exécuter l'opération ou dans le cas où l'opération permet au même utilisateur de s'exécuter, nous validons que l'ID sur les paramètres de la requête est le même que celui du jeton d'authentification. Si l'utilisateur n'a pas le rôle requis, nous renverrons un 403.

 import { Request, Response } from "express"; export function isAuthorized(opts: { hasRole: Array<'admin' | 'manager' | 'user'>, allowSameUser?: boolean }) { return (req: Request, res: Response, next: Function) => { const { role, email, uid } = res.locals const { id } = req.params if (opts.allowSameUser && id && uid === id) return next(); if (!role) return res.status(403).send(); if (opts.hasRole.includes(role)) return next(); return res.status(403).send(); } }

Avec ces deux méthodes, nous pourrons authentifier les requêtes et les autoriser en fonction du role dans le jeton entrant. C'est très bien, mais comme Firebase ne nous permet pas de définir des revendications personnalisées à partir de la console du projet, nous ne pourrons exécuter aucun de ces points de terminaison. Afin de contourner cela, nous pouvons créer un utilisateur root à partir de Firebase Authentication Console

Créer un utilisateur à partir de la console d'authentification Firebase

Et définissez une comparaison d'e-mails dans le code. Désormais, lors du déclenchement des requêtes de cet utilisateur, nous pourrons exécuter toutes les opérations.

 //... const { role, email, uid } = res.locals const { id } = req.params if (email === '[email protected]') return next(); //...

Maintenant, ajoutons le reste des opérations CRUD à src/users/routes-config.ts .

Pour les opérations visant à obtenir ou à mettre à jour un seul utilisateur où :id param est envoyé, nous autorisons également le même utilisateur à exécuter l'opération.

 export function routesConfig(app: Application) { //.. // lists all users app.get('/users', [ isAuthenticated, isAuthorized({ hasRole: ['admin', 'manager'] }), all ]); // get :id user app.get('/users/:id', [ isAuthenticated, isAuthorized({ hasRole: ['admin', 'manager'], allowSameUser: true }), get ]); // updates :id user app.patch('/users/:id', [ isAuthenticated, isAuthorized({ hasRole: ['admin', 'manager'], allowSameUser: true }), patch ]); // deletes :id user app.delete('/users/:id', [ isAuthenticated, isAuthorized({ hasRole: ['admin', 'manager'] }), remove ]); }

Et sur src/users/controller.ts . Dans ces opérations, nous utilisons le SDK d'administration pour interagir avec Firebase Authentication et effectuer les opérations respectives. Comme nous l'avons fait précédemment sur l'opération de create , nous retournons un code HTTP significatif sur chaque opération.

Pour l'opération de mise à jour, nous validons tous les champs présents et customClaims par ceux envoyés dans la requête :

 //.. export async function all(req: Request, res: Response) { try { const listUsers = await admin.auth().listUsers() const users = listUsers.users.map(mapUser) return res.status(200).send({ users }) } catch (err) { return handleError(res, err) } } function mapUser(user: admin.auth.UserRecord) { const customClaims = (user.customClaims || { role: '' }) as { role?: string } const role = customClaims.role ? customClaims.role : '' return { uid: user.uid, email: user.email || '', displayName: user.displayName || '', role, lastSignInTime: user.metadata.lastSignInTime, creationTime: user.metadata.creationTime } } export async function get(req: Request, res: Response) { try { const { id } = req.params const user = await admin.auth().getUser(id) return res.status(200).send({ user: mapUser(user) }) } catch (err) { return handleError(res, err) } } export async function patch(req: Request, res: Response) { try { const { id } = req.params const { displayName, password, email, role } = req.body if (!id || !displayName || !password || !email || !role) { return res.status(400).send({ message: 'Missing fields' }) } await admin.auth().updateUser(id, { displayName, password, email }) await admin.auth().setCustomUserClaims(id, { role }) const user = await admin.auth().getUser(id) return res.status(204).send({ user: mapUser(user) }) } catch (err) { return handleError(res, err) } } export async function remove(req: Request, res: Response) { try { const { id } = req.params await admin.auth().deleteUser(id) return res.status(204).send({}) } catch (err) { return handleError(res, err) } } //...

Nous pouvons maintenant exécuter la fonction localement. Pour ce faire, vous devez d'abord configurer la clé de compte pour pouvoir vous connecter localement à l'API d'authentification. Exécutez ensuite :

 npm run serve

Déployer l'API

Génial! Maintenant que nous avons écrit l'API basée sur les rôles, nous pouvons la déployer sur le Web et commencer à l'utiliser. Le déploiement avec Firebase est super facile, nous avons juste besoin d'exécuter firebase deploy . Une fois le déploiement terminé, nous pouvons accéder à notre API à l'URL publiée.

Exécuter la commande firebase deploy

Vous pouvez vérifier l'URL de l'API sur https://console.firebase.google.com/u/0/project/{your-project}/functions/list.

L'URL de l'API sur la console Firebase

Dans mon cas, c'est [https://us-central1-joaq-lab.cloudfunctions.net/api].

Consommer l'API

Une fois notre API déployée, nous avons plusieurs façons de l'utiliser. Dans ce tutoriel, je vais expliquer comment l'utiliser via Postman ou depuis une application Angular.

Si nous saisissons l'URL List All Users ( /api/users ) sur n'importe quel navigateur, nous obtiendrons ce qui suit :

API d'authentification Firebase

La raison en est que lors de l'envoi de la requête depuis un navigateur, nous effectuons une requête GET sans en-têtes d'authentification. Cela signifie que notre API fonctionne comme prévu !

Notre API est sécurisée via des jetons. Pour générer un tel jeton, nous devons appeler le SDK client de Firebase et nous connecter avec un identifiant utilisateur/mot de passe valide. En cas de succès, Firebase renverra un jeton dans la réponse que nous pourrons ensuite ajouter à l'en-tête de toute requête suivante que nous souhaitons effectuer.

Depuis une application angulaire

Dans ce tutoriel, je vais juste passer en revue les éléments importants pour consommer l'API à partir d'une application Angular. Le référentiel complet est accessible ici, et si vous avez besoin d'un tutoriel étape par étape sur la façon de créer une application angulaire et de configurer @angular/fire à utiliser, vous pouvez consulter ce post.

Donc, revenons à la connexion, nous aurons un SignInComponent avec un <form> pour permettre à l'utilisateur d'entrer un nom d'utilisateur et un mot de passe.

 //... <form [formGroup]="form"> <div class="form-group"> <label>Email address</label> <input type="email" formControlName="email" class="form-control" placeholder="Enter email"> </div> <div class="form-group"> <label>Password</label> <input type="password" formControlName="password" class="form-control" placeholder="Password"> </div> </form> //...

Et sur la classe, nous signInWithEmailAndPassword en utilisant le service AngularFireAuth .

 //... form: FormGroup = new FormGroup({ email: new FormControl(''), password: new FormControl('') }) constructor( private afAuth: AngularFireAuth ) { } async signIn() { try { const { email, password } = this.form.value await this.afAuth.auth.signInWithEmailAndPassword(email, password) } catch (err) { console.log(err) } } //..

À ce stade, nous pouvons nous connecter à notre projet Firebase.

Connexion via l'application Angular

La réponse de l'API lors de la connexion à partir de l'application Angular

Et lorsque nous inspectons les requêtes réseau dans les DevTools, nous pouvons voir que Firebase renvoie un jeton après avoir vérifié notre utilisateur et notre mot de passe.

Ce jeton est celui que nous utiliserons pour envoyer la demande de notre en-tête à l'API que nous avons construite. Une façon d'ajouter le jeton à toutes les requêtes consiste à utiliser un HttpInterceptor .

Ce fichier montre comment obtenir le jeton d' AngularFireAuth et l'ajouter à la demande de l'en-tête. Nous fournissons ensuite le fichier intercepteur dans le AppModule.

http-interceptors/auth-token.interceptor.ts

 @Injectable({ providedIn: 'root' }) export class AuthTokenHttpInterceptor implements HttpInterceptor { constructor( private auth: AngularFireAuth ) { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return this.auth.idToken.pipe( take(1), switchMap(idToken => { let clone = req.clone() if (idToken) { clone = clone.clone({ headers: req.headers.set('Authorization', 'Bearer ' + idToken) }); } return next.handle(clone) }) ) } } export const AuthTokenHttpInterceptorProvider = { provide: HTTP_INTERCEPTORS, useClass: AuthTokenHttpInterceptor, multi: true }

app.module.ts

 @NgModule({ //.. providers: [ AuthTokenHttpInterceptorProvider ] //... }) export class AppModule { }

Une fois l'intercepteur défini, nous pouvons faire des requêtes à notre API depuis httpClient . Par exemple, voici un UsersService où nous appelons la liste tous les utilisateurs, récupérons l'utilisateur par son ID, créons un utilisateur et mettons à jour un utilisateur.

 //… export type CreateUserRequest = { displayName: string, password: string, email: string, role: string } export type UpdateUserRequest = { uid: string } & CreateUserRequest @Injectable({ providedIn: 'root' }) export class UserService { private baseUrl = '{your-functions-url}/api/users' constructor( private http: HttpClient ) { } get users$(): Observable<User[]> { return this.http.get<{ users: User[] }>(`${this.baseUrl}`).pipe( map(result => { return result.users }) ) } user$(id: string): Observable<User> { return this.http.get<{ user: User }>(`${this.baseUrl}/${id}`).pipe( map(result => { return result.user }) ) } create(user: CreateUserRequest) { return this.http.post(`${this.baseUrl}`, user) } edit(user: UpdateUserRequest) { return this.http.patch(`${this.baseUrl}/${user.uid}`, user) } }

Maintenant, nous pouvons appeler l'API pour obtenir l'utilisateur connecté par son ID et lister tous les utilisateurs d'un composant comme celui-ci :

 //... <div *ngIf="user$ | async; let user" class="col-12"> <div class="d-flex justify-content-between my-3"> <h4> Me </h4> </div> <ul class="list-group"> <li class="list-group-item d-flex justify-content-between align-items-center"> <div> <h5 class="mb-1">{{user.displayName}}</h5> <small>{{user.email}}</small> </div> <span class="badge badge-primary badge-pill">{{user.role?.toUpperCase()}}</span> </li> </ul> </div> <div class="col-12"> <div class="d-flex justify-content-between my-3"> <h4> All Users </h4> </div> <ul *ngIf="users$ | async; let users" class="list-group"> <li *ngFor="let user of users" class="list-group-item d-flex justify-content-between align-items-center"> <div> <h5 class="mb-1">{{user.displayName}}</h5> <small class="d-block">{{user.email}}</small> <small class="d-block">{{user.uid}}</small> </div> <span class="badge badge-primary badge-pill">{{user.role?.toUpperCase()}}</span> </li> </ul> //...
 //... users$: Observable<User[]> user$: Observable<User> constructor( private userService: UserService, private userForm: UserFormService, private modal: NgbModal, private afAuth: AngularFireAuth ) { } ngOnInit() { this.users$ = this.userService.users$ this.user$ = this.afAuth.user.pipe( filter(user => !!user), switchMap(user => this.userService.user$(user.uid)) ) } //...

Et voici le résultat.

Tous les utilisateurs de notre application Angular

Notez que si nous nous connectons avec un utilisateur avec role=user , seule la section Me sera rendue.

La vue de la ressource utilisateur à laquelle l'utilisateur avec le rôle utilisateur a accès

Et nous aurons un 403 sur l'inspecteur du réseau. Cela est dû à la restriction que nous avons définie auparavant sur l'API pour autoriser uniquement les "administrateurs" à répertorier tous les utilisateurs.

Une erreur 403 dans l'inspecteur réseau

Maintenant, ajoutons les fonctionnalités "créer un utilisateur" et "modifier un utilisateur". Pour ce faire, créons d'abord un UserFormComponent et un UserFormService .

 <ng-container *ngIf="user$ | async"></ng-container> <div class="modal-header"> <h4 class="modal-title">{{ title$ | async}}</h4> <button type="button" class="close" (click)="dismiss()"> <span aria-hidden="true">&times;</span> </button> </div> <div class="modal-body"> <form [formGroup]="form" (ngSubmit)="save()"> <div class="form-group"> <label>Email address</label> <input type="email" formControlName="email" class="form-control" placeholder="Enter email"> </div> <div class="form-group"> <label>Password</label> <input type="password" formControlName="password" class="form-control" placeholder="Password"> </div> <div class="form-group"> <label>Display Name</label> <input type="string" formControlName="displayName" class="form-control" placeholder="Enter display name"> </div> <div class="form-group"> <label>Role</label> <select class="custom-select" formControlName="role"> <option value="admin">Admin</option> <option value="manager">Manager</option> <option value="user">User</option> </select> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-outline-danger" (click)="dismiss()">Cancel</button> <button type="button" class="btn btn-primary" (click)="save()">Save</button> </div>
 @Component({ selector: 'app-user-form', templateUrl: './user-form.component.html', styleUrls: ['./user-form.component.scss'] }) export class UserFormComponent implements OnInit { form = new FormGroup({ uid: new FormControl(''), email: new FormControl(''), displayName: new FormControl(''), password: new FormControl(''), role: new FormControl(''), }); title$: Observable<string>; user$: Observable<{}>; constructor( public modal: NgbActiveModal, private userService: UserService, private userForm: UserFormService ) { } ngOnInit() { this.title$ = this.userForm.title$; this.user$ = this.userForm.user$.pipe( tap(user => { if (user) { this.form.patchValue(user); } else { this.form.reset({}); } }) ); } dismiss() { this.modal.dismiss('modal dismissed'); } save() { const { displayName, email, role, password, uid } = this.form.value; this.modal.close({ displayName, email, role, password, uid }); } }
 import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class UserFormService { _BS = new BehaviorSubject({ title: '', user: {} }); constructor() { } edit(user) { this._BS.next({ title: 'Edit User', user }); } create() { this._BS.next({ title: 'Create User', user: null }); } get title$() { return this._BS.asObservable().pipe( map(uf => uf.title) ); } get user$() { return this._BS.asObservable().pipe( map(uf => uf.user) ); } }

De retour dans le composant principal, ajoutons les boutons pour appeler ces actions. Dans ce cas, "Modifier l'utilisateur" ne sera disponible que pour l'utilisateur connecté. Vous pouvez continuer et ajouter la fonctionnalité pour modifier d'autres utilisateurs si vous en avez besoin !

 //... <div class="d-flex justify-content-between my-3"> <h4> Me </h4> <button class="btn btn-primary" (click)="edit(user)"> Edit Profile </button> </div> //... <div class="d-flex justify-content-between my-3"> <h4> All Users </h4> <button class="btn btn-primary" (click)="create()"> New User </button> </div> //...
 //... create() { this.userForm.create(); const modalRef = this.modal.open(UserFormComponent); modalRef.result.then(user => { this.userService.create(user).subscribe(_ => { console.log('user created'); }); }).catch(err => { }); } edit(userToEdit) { this.userForm.edit(userToEdit); const modalRef = this.modal.open(UserFormComponent); modalRef.result.then(user => { this.userService.edit(user).subscribe(_ => { console.log('user edited'); }); }).catch(err => { }); }

Du facteur

Postman est un outil pour construire et faire des requêtes aux API. De cette façon, nous pouvons simuler que nous appelons notre API à partir de n'importe quelle application cliente ou d'un service différent.

Ce que nous allons démontrer, c'est comment envoyer une demande pour répertorier tous les utilisateurs.

Une fois l'outil ouvert, nous définissons l'URL https://us-central1-{your-project}.cloudfunctions.net/api/users :

L'URL de l'API chargée dans le champ Postman prête à être déclenchée en tant que requête GET

Ensuite, sur l'onglet autorisation, nous choisissons Bearer Token et nous définissons la valeur que nous avons précédemment extraite des outils de développement.

Définition du jeton du porteur dans Postman

Le corps de la réponse que nous recevons

Conclusion

Toutes nos félicitations! Vous avez parcouru tout le didacticiel et vous avez maintenant appris à créer une API basée sur les rôles utilisateur sur Firebase.

Nous avons également expliqué comment le consommer à partir d'une application angulaire et de Postman.

Récapitulons les choses les plus importantes :

  1. Firebase vous permet d'être rapidement opérationnel avec une API d'authentification au niveau de l'entreprise, que vous pouvez étendre ultérieurement.
  2. Presque tous les projets nécessitent une autorisation. Si vous avez besoin de contrôler l'accès à l'aide d'un modèle basé sur les rôles, Firebase Authentication vous permet de démarrer très rapidement.
  3. Le modèle basé sur les rôles s'appuie sur la validation des ressources qui sont demandées aux utilisateurs avec des rôles spécifiques par rapport aux utilisateurs spécifiques.
  4. À l'aide d'une application Express.js sur Firebase Function, nous pouvons créer une API REST et définir des gestionnaires pour authentifier et autoriser les demandes.
  5. En tirant parti des revendications personnalisées intégrées, vous pouvez créer une API d'authentification basée sur les rôles et sécuriser votre application.

Vous pouvez en savoir plus sur l'authentification Firebase ici. Et si vous souhaitez tirer parti des rôles que nous avons définis, vous pouvez utiliser @angular/fire helpers.