So erstellen Sie eine rollenbasierte API mit Firebase-Authentifizierung
Veröffentlicht: 2022-03-11In 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.
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
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
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.
Sie können die API-URL unter https://console.firebase.google.com/u/0/project/{your-project}/functions/list überprüfen.
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:
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.
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.
Beachten Sie, dass bei der Anmeldung mit einem Benutzer mit role=user
nur der Me-Abschnitt gerendert wird.
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.
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">×</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:
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.
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:
- Firebase ermöglicht Ihnen eine schnelle Inbetriebnahme mit einer Authentifizierungs-API auf Unternehmensebene, die Sie später erweitern können.
- 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.
- Das rollenbasierte Modell basiert auf der Validierung von Ressourcen, die von Benutzern mit bestimmten Rollen im Vergleich zu bestimmten Benutzern angefordert werden.
- 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.
- 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.