So erstellen Sie eine rollenbasierte API mit Firebase-Authentifizierung

Veröffentlicht: 2022-03-11

In diesem Tutorial erstellen wir eine REST-API zum Verwalten von Benutzern und Rollen mit Firebase und Node.js. Darüber hinaus werden wir sehen, wie die API verwendet wird, um zu autorisieren (oder nicht), welche Benutzer auf bestimmte Ressourcen zugreifen können.

Einführung

Fast jede App erfordert ein gewisses Maß an Autorisierungssystem. In einigen Fällen reicht es aus, einen Benutzernamen/ein Kennwort mit unserer Benutzertabelle zu validieren, aber oft benötigen wir ein differenzierteres Berechtigungsmodell, um bestimmten Benutzern den Zugriff auf bestimmte Ressourcen zu ermöglichen und sie von anderen einzuschränken. Der Aufbau eines Systems zur Unterstützung des Letzteren ist nicht trivial und kann sehr zeitaufwändig sein. In diesem Tutorial erfahren wir, wie Sie mit Firebase eine rollenbasierte Authentifizierungs-API erstellen, die uns dabei helfen wird, schnell einsatzbereit zu sein.

Rollenbasierte Auth

In diesem Autorisierungsmodell wird der Zugriff Rollen statt bestimmten Benutzern gewährt, und ein Benutzer kann eine oder mehrere haben, je nachdem, wie Sie Ihr Berechtigungsmodell entwerfen. Ressourcen hingegen erfordern bestimmte Rollen, damit ein Benutzer sie ausführen kann.

Rollenbasierte Authentifizierung mit illustriertem

Firebase

Firebase-Authentifizierung

Kurz gesagt, Firebase Authentication ist ein erweiterbares Token-basiertes Authentifizierungssystem und bietet sofort einsatzbereite Integrationen mit den gängigsten Anbietern wie unter anderem Google, Facebook und Twitter.

Es ermöglicht uns, benutzerdefinierte Ansprüche zu verwenden, die wir nutzen, um eine flexible rollenbasierte API zu erstellen.

Wir können jeden JSON-Wert in die Ansprüche setzen (z. B. { role: 'admin' } oder { role: 'manager' } ).

Einmal festgelegt, werden benutzerdefinierte Ansprüche in das von Firebase generierte Token aufgenommen, und wir können den Wert lesen, um den Zugriff zu steuern.

Es kommt auch mit einem sehr großzügigen kostenlosen Kontingent, das in den meisten Fällen mehr als genug sein wird.

Firebase-Funktionen

Funktionen sind ein vollständig verwalteter serverloser Plattformdienst. Wir müssen nur unseren Code in Node.js schreiben und bereitstellen. Firebase kümmert sich um die Skalierung der Infrastruktur nach Bedarf, die Serverkonfiguration und mehr. In unserem Fall verwenden wir es, um unsere API zu erstellen und sie über HTTP im Web verfügbar zu machen.

Firebase ermöglicht es uns, express.js -Apps als Handler für verschiedene Pfade festzulegen – Sie können beispielsweise eine Express-App erstellen und sie mit /mypath , und alle Anfragen, die auf dieser Route eingehen, werden von der konfigurierten app verarbeitet.

Innerhalb des Kontexts einer Funktion haben Sie mithilfe des Admin SDK Zugriff auf die gesamte Firebase-Authentifizierungs-API.

So erstellen wir die Benutzer-API.

Was wir bauen

Bevor wir also anfangen, werfen wir einen Blick darauf, was wir bauen werden. Wir werden eine REST-API mit den folgenden Endpunkten erstellen:

HTTP-Verb Weg Beschreibung Genehmigung
BEKOMMEN /Benutzer Listet alle Benutzer auf Nur Administratoren und Manager haben Zugriff
POST /Benutzer Erstellt einen neuen Benutzer Nur Administratoren und Manager haben Zugriff
BEKOMMEN /users/:id Ruft den :id-Benutzer ab Administratoren, Manager und derselbe Benutzer wie :id haben Zugriff
PATCH /users/:id Aktualisiert den :id-Benutzer Administratoren, Manager und derselbe Benutzer wie :id haben Zugriff
LÖSCHEN /users/:id Löscht den :id-Benutzer Administratoren, Manager und derselbe Benutzer wie :id haben Zugriff

Jeder dieser Endpunkte verarbeitet die Authentifizierung, validiert die Autorisierung, führt die entsprechende Operation aus und gibt schließlich einen aussagekräftigen HTTP-Code zurück.

Wir erstellen die Authentifizierungs- und Autorisierungsfunktionen, die zum Validieren des Tokens erforderlich sind, und prüfen, ob die Ansprüche die erforderliche Rolle zum Ausführen des Vorgangs enthalten.

Erstellen der API

Um die API zu erstellen, benötigen wir:

  • Ein Firebase-Projekt
  • firebase-tools installiert

Melden Sie sich zuerst bei Firebase an:

 firebase login

Initialisieren Sie als Nächstes ein Functions-Projekt:

 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

An diesem Punkt haben Sie einen Funktionsordner mit minimaler Einrichtung zum Erstellen von Firebase-Funktionen.

src/index.ts gibt es ein helloWorld -Beispiel, das Sie auskommentieren können, um zu überprüfen, ob Ihre Funktionen funktionieren. Dann können Sie cd functions und npm run serve . Dieser Befehl transpiliert den Code und startet den lokalen Server.

Sie können die Ergebnisse unter http://localhost:5000/{your-project}/us-central1/helloWorld überprüfen

Eine frische Firebase-App

Beachten Sie, dass die Funktion auf dem Pfad verfügbar gemacht wird, der als Name unter 'index.ts: 'helloWorld' .

Erstellen einer Firebase-HTTP-Funktion

Lassen Sie uns nun unsere API codieren. Wir werden eine http-Firebase-Funktion erstellen und sie in den /api -Pfad einhängen.

Installieren Sie zuerst npm install express .

Auf src/index.ts werden wir:

  • Initialisieren Sie das firebase-admin SDK-Modul mit admin.initializeApp();
  • Legen Sie eine Express-App als Handler unseres api -https-Endpunkts fest
 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);

Jetzt werden alle Anfragen an /api von der app Instanz verarbeitet.

Als Nächstes konfigurieren wir die app -Instanz so, dass sie CORS unterstützt, und fügen JSON-Body-Parser-Middleware hinzu. Auf diese Weise können wir Anfragen von jeder URL aus stellen und Anfragen im JSON-Format parsen.

Wir werden zuerst die erforderlichen Abhängigkeiten installieren.

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

Und dann:

 //... 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);

Schließlich werden wir die Routen konfigurieren, die die app handhaben wird.

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

Firebase Functions ermöglicht es uns, eine Express-App als Handler und jeden Pfad nach dem Pfad festzulegen, den Sie unter functions.https.onRequest(app); – in diesem Fall api – wird ebenfalls von der app verarbeitet. Dadurch können wir spezifische Endpunkte wie api/users schreiben und einen Handler für jedes HTTP-Verb festlegen, was wir als Nächstes tun werden.

Lassen Sie uns die Datei src/users/routes-config.ts

Hier setzen wir einen create -Handler bei POST '/users'

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

Jetzt erstellen wir die Datei src/users/controller.ts .

In dieser Funktion validieren wir zuerst, dass alle Felder in der Body-Anforderung enthalten sind, und als Nächstes erstellen wir den Benutzer und legen die benutzerdefinierten Ansprüche fest.

Wir übergeben nur { role } in setCustomUserClaims – die anderen Felder werden bereits von Firebase festgelegt.

Wenn keine Fehler auftreten, geben wir einen 201-Code mit der uid des erstellten Benutzers zurück.

 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}` }); }

Lassen Sie uns nun den Handler sichern, indem wir eine Autorisierung hinzufügen. Dazu fügen wir unserem create -Endpunkt ein paar Handler hinzu. Mit express.js können Sie eine Kette von Handlern festlegen, die der Reihe nach ausgeführt werden. Innerhalb eines Handlers können Sie Code ausführen und an den next() Handler übergeben oder eine Antwort zurückgeben. Was wir tun, ist zuerst den Benutzer zu authentifizieren und dann zu validieren, ob er zur Ausführung autorisiert ist.

In der Datei 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 ); }

Lassen Sie uns die Dateien src/auth/authenticated.ts erstellen.

Bei dieser Funktion validieren wir das Vorhandensein des authorization im Anforderungsheader. Dann dekodieren wir es mit admin.auth().verifyidToken() und behalten die uid , role und email des Benutzers in der res.locals bei, die wir später verwenden, um die Autorisierung zu validieren.

Falls das Token ungültig ist, senden wir eine 401-Antwort an den Client zurück:

 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' }); } }

Lassen Sie uns nun eine src/auth/authorized.ts -Datei erstellen.

In diesem Handler extrahieren wir die Benutzerinformationen aus res.locals , die wir zuvor festgelegt haben, und validieren, ob sie die erforderliche Rolle zum Ausführen der Operation haben, oder falls die Operation die Ausführung durch denselben Benutzer zulässt, validieren wir die ID in den Anforderungsparametern ist die gleiche wie die im Auth-Token. Wenn der Benutzer nicht die erforderliche Rolle hat, geben wir 403 zurück.

 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(); } }

Mit diesen beiden Methoden können wir Anfragen authentifizieren und sie anhand der role im eingehenden Token autorisieren. Das ist großartig, aber da wir mit Firebase keine benutzerdefinierten Ansprüche über die Projektkonsole festlegen können, können wir keinen dieser Endpunkte ausführen. Um dies zu umgehen, können wir über die Firebase Authentication Console einen Root-Benutzer erstellen

Erstellen eines Benutzers über die Firebase-Authentifizierungskonsole

Und setzen Sie einen E-Mail-Vergleich im Code. Wenn wir jetzt Anfragen von diesem Benutzer senden, können wir alle Operationen ausführen.

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

Lassen Sie uns nun den Rest der CRUD-Operationen zu src/users/routes-config.ts .

Für Operationen zum Abrufen oder Aktualisieren eines einzelnen Benutzers, bei dem :id param gesendet wird, erlauben wir auch demselben Benutzer, die Operation auszuführen.

 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 ]); }

Und auf src/users/controller.ts . Bei diesen Vorgängen nutzen wir das Admin-SDK, um mit der Firebase-Authentifizierung zu interagieren und die entsprechenden Vorgänge auszuführen. Wie zuvor bei der create geben wir bei jeder Operation einen aussagekräftigen HTTP-Code zurück.

Für den Aktualisierungsvorgang validieren wir alle vorhandenen Felder und überschreiben customClaims mit denen, die in der Anfrage gesendet wurden:

 //.. 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) } } //...

Jetzt können wir die Funktion lokal ausführen. Dazu müssen Sie zunächst den Kontoschlüssel einrichten, um sich lokal mit der Authentifizierungs-API verbinden zu können. Dann renne:

 npm run serve

Stellen Sie die API bereit

Toll! Nachdem wir die rollenbasierte API geschrieben haben, können wir sie im Web bereitstellen und mit der Verwendung beginnen. Die Bereitstellung mit Firebase ist super einfach, wir müssen nur firebase deploy ausführen. Sobald die Bereitstellung abgeschlossen ist, können wir über die veröffentlichte URL auf unsere API zugreifen.

Ausführen des Firebase-Bereitstellungsbefehls

Sie können die API-URL unter https://console.firebase.google.com/u/0/project/{your-project}/functions/list überprüfen.

Die API-URL in der Firebase-Konsole

In meinem Fall ist es [https://us-central1-joaq-lab.cloudfunctions.net/api].

Konsumieren der API

Sobald unsere API bereitgestellt ist, haben wir mehrere Möglichkeiten, sie zu verwenden – in diesem Tutorial werde ich behandeln, wie sie über Postman oder über eine Angular-App verwendet wird.

Wenn wir die URL „Liste aller Benutzer“ ( /api/users ) in einem beliebigen Browser eingeben, erhalten wir Folgendes:

Firebase-Authentifizierungs-API

Der Grund dafür ist, dass wir beim Senden der Anfrage von einem Browser eine GET-Anfrage ohne Auth-Header ausführen. Das bedeutet, dass unsere API tatsächlich wie erwartet funktioniert!

Unsere API ist über Token gesichert – um ein solches Token zu generieren, müssen wir das Client SDK von Firebase aufrufen und uns mit gültigen Benutzer-/Passwort-Anmeldeinformationen anmelden. Bei Erfolg sendet Firebase in der Antwort ein Token zurück, das wir dann dem Header jeder folgenden Anfrage hinzufügen können, die wir ausführen möchten.

Aus einer Angular-App

In diesem Tutorial gehe ich nur die wichtigen Teile durch, um die API aus einer Angular-App zu nutzen. Auf das vollständige Repository kann hier zugegriffen werden, und wenn Sie eine Schritt-für-Schritt-Anleitung zum Erstellen einer Angular-App und zum Konfigurieren von @angular/fire für die Verwendung benötigen, können Sie diesen Beitrag lesen.

Zurück zur Anmeldung, wir haben eine SignInComponent mit einem <form> , damit der Benutzer einen Benutzernamen und ein Kennwort eingeben kann.

 //... <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> //...

Und in der Klasse signInWithEmailAndPassword verwenden wir den Dienst 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) } } //..

An diesem Punkt können wir uns bei unserem Firebase-Projekt anmelden.

Anmeldung über die Angular-App

Die API-Antwort bei der Anmeldung über die Angular-App

Und wenn wir die Netzwerkanforderungen in den DevTools untersuchen, können wir sehen, dass Firebase ein Token zurückgibt, nachdem wir unseren Benutzer und unser Passwort überprüft haben.

Dieses Token verwenden wir, um die Anfrage unseres Headers an die von uns erstellte API zu senden. Eine Möglichkeit, das Token allen Anforderungen hinzuzufügen, ist die Verwendung eines HttpInterceptor .

Diese Datei zeigt, wie Sie das Token von AngularFireAuth und es der Header-Anforderung hinzufügen. Wir stellen dann die Interceptor-Datei im AppModule bereit.

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 { }

Sobald der Interceptor eingerichtet ist, können wir Anfragen an unsere API von httpClient . Hier ist zum Beispiel ein UsersService , bei dem wir die Liste alle Benutzer nennen, den Benutzer anhand seiner ID abrufen, einen Benutzer erstellen und einen Benutzer aktualisieren.

 //… 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) } }

Jetzt können wir die API aufrufen, um den angemeldeten Benutzer anhand seiner ID abzurufen und alle Benutzer von einer Komponente wie dieser aufzulisten:

 //... <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)) ) } //...

Und hier ist das Ergebnis.

Alle Benutzer in unserer Angular-App

Beachten Sie, dass bei der Anmeldung mit einem Benutzer mit role=user nur der Me-Abschnitt gerendert wird.

Die Ansicht der Benutzerressource, auf die der Benutzer mit der Rolle Benutzer Zugriff hat

Und wir bekommen einen 403 auf dem Netzwerkinspektor. Dies liegt an der Einschränkung, die wir zuvor für die API festgelegt haben, um nur „Admins“ zu erlauben, alle Benutzer aufzulisten.

Ein 403-Fehler im Netzwerkinspektor

Lassen Sie uns nun die Funktionen „Benutzer erstellen“ und „Benutzer bearbeiten“ hinzufügen. Dazu erstellen wir zunächst eine UserFormComponent und einen 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) ); } }

Zurück in der Hauptkomponente fügen wir die Schaltflächen hinzu, um diese Aktionen aufzurufen. In diesem Fall steht „Benutzer bearbeiten“ nur dem angemeldeten Benutzer zur Verfügung. Sie können fortfahren und die Funktionalität hinzufügen, um andere Benutzer zu bearbeiten, wenn Sie dies benötigen!

 //... <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 => { }); }

Vom Postboten

Postman ist ein Tool zum Erstellen und Senden von Anfragen an APIs. Auf diese Weise können wir simulieren, dass wir unsere API von einer beliebigen Client-App oder einem anderen Dienst aufrufen.

Was wir demonstrieren, ist, wie man eine Anfrage sendet, um alle Benutzer aufzulisten.

Sobald wir das Tool öffnen, setzen wir die URL https://us-central1-{your-project}.cloudfunctions.net/api/users:

Die in das Postman-Feld geladene API-URL, die als GET-Anforderung ausgelöst werden kann

Als nächstes wählen wir auf der Registerkarte Autorisierung Bearer Token und legen den Wert fest, den wir zuvor aus Dev Tools extrahiert haben.

Festlegen des Inhabertokens in Postman

Der Text der Antwort, die wir erhalten

Fazit

Glückwünsche! Sie haben das gesamte Tutorial durchgearbeitet und nun gelernt, wie man eine benutzerrollenbasierte API auf Firebase erstellt.

Wir haben auch behandelt, wie man es von einer Angular-App und Postman aus verwendet.

Fassen wir das Wichtigste noch einmal zusammen:

  1. Firebase ermöglicht Ihnen eine schnelle Inbetriebnahme mit einer Authentifizierungs-API auf Unternehmensebene, die Sie später erweitern können.
  2. Fast jedes Projekt erfordert eine Autorisierung – wenn Sie den Zugriff mithilfe eines rollenbasierten Modells steuern müssen, können Sie mit Firebase Authentication sehr schnell loslegen.
  3. Das rollenbasierte Modell basiert auf der Validierung von Ressourcen, die von Benutzern mit bestimmten Rollen im Vergleich zu bestimmten Benutzern angefordert werden.
  4. Mit einer Express.js-App auf Firebase Function können wir eine REST-API erstellen und Handler festlegen, um Anfragen zu authentifizieren und zu autorisieren.
  5. Durch die Nutzung integrierter benutzerdefinierter Ansprüche können Sie eine rollenbasierte Authentifizierungs-API erstellen und Ihre App sichern.

Weitere Informationen zur Firebase-Authentifizierung finden Sie hier. Und wenn Sie die von uns definierten Rollen nutzen möchten, können Sie @angular/fire-Helfer verwenden.