Jak zbudować interfejs API oparty na rolach za pomocą uwierzytelniania Firebase

Opublikowany: 2022-03-11

W tym samouczku zbudujemy interfejs API REST do zarządzania użytkownikami i rolami za pomocą Firebase i Node.js. Ponadto zobaczymy, jak używać interfejsu API do autoryzacji (lub nie), którzy użytkownicy mogą uzyskać dostęp do określonych zasobów.

Wstęp

Prawie każda aplikacja wymaga pewnego poziomu systemu autoryzacji. W niektórych przypadkach wystarczy zweryfikowanie zestawu nazwy użytkownika/hasła za pomocą naszej tabeli Użytkownicy, ale często potrzebujemy bardziej szczegółowego modelu uprawnień, aby umożliwić niektórym użytkownikom dostęp do niektórych zasobów i ograniczyć je od innych. Zbudowanie systemu do obsługi tych ostatnich nie jest trywialne i może być bardzo czasochłonne. W tym samouczku dowiemy się, jak zbudować oparty na rolach interfejs API uwierzytelniania przy użyciu Firebase, co pomoże nam szybko rozpocząć pracę.

Uwierzytelnianie oparte na rolach

W tym modelu autoryzacji dostęp jest przyznawany rolom, a nie określonym użytkownikom, a użytkownik może mieć jeden lub więcej w zależności od sposobu projektowania modelu uprawnień. Z drugiej strony zasoby wymagają określonych ról, aby umożliwić użytkownikowi ich wykonanie.

Uwierzytelnianie oparte na rolach z ilustracją

Firebase

Uwierzytelnianie Firebase

W skrócie, Firebase Authentication to rozszerzalny system uwierzytelniania oparty na tokenach i zapewnia gotowe integracje z najpopularniejszymi dostawcami, takimi jak między innymi Google, Facebook i Twitter.

Umożliwia nam korzystanie z oświadczeń niestandardowych, które wykorzystamy do zbudowania elastycznego interfejsu API opartego na rolach.

W oświadczeniach możemy ustawić dowolną wartość JSON (np. { role: 'admin' } lub { role: 'manager' } ).

Po ustawieniu oświadczenia niestandardowe zostaną uwzględnione w tokenie generowanym przez Firebase i będziemy mogli odczytać wartość, aby kontrolować dostęp.

Zawiera również bardzo hojny darmowy limit, który w większości przypadków będzie więcej niż wystarczający.

Funkcje Firebase

Funkcje to w pełni zarządzana usługa platformy bezserwerowej. Wystarczy napisać nasz kod w Node.js i wdrożyć go. Firebase zajmuje się skalowaniem infrastruktury na żądanie, konfiguracją serwera i nie tylko. W naszym przypadku użyjemy go do zbudowania naszego API i udostępnienia go przez HTTP w sieci.

Firebase pozwala nam ustawić aplikacje express.js jako programy obsługi dla różnych ścieżek — na przykład możesz utworzyć aplikację Express i podłączyć ją do /mypath , a wszystkie żądania przychodzące do tej trasy będą obsługiwane przez skonfigurowaną app .

Z poziomu kontekstu funkcji masz dostęp do całego interfejsu Firebase Authentication API za pomocą pakietu Admin SDK.

W ten sposób stworzymy interfejs API użytkownika.

Co zbudujemy

Więc zanim zaczniemy, spójrzmy, co zbudujemy. Zamierzamy stworzyć REST API z następującymi punktami końcowymi:

Czasownik HTTP Ścieżka Opis Upoważnienie
DOSTWAĆ /użytkownicy Wyświetla listę wszystkich użytkowników Dostęp mają tylko administratorzy i menedżerowie
POCZTA /użytkownicy Tworzy nowego użytkownika Dostęp mają tylko administratorzy i menedżerowie
DOSTWAĆ /użytkownicy/:identyfikator Pobiera :id użytkownika Administratorzy, menedżerowie i ten sam użytkownik co :id mają dostęp
SKRAWEK /użytkownicy/:identyfikator Aktualizuje :id użytkownika Administratorzy, menedżerowie i ten sam użytkownik co :id mają dostęp
USUNĄĆ /użytkownicy/:identyfikator Usuwa :id użytkownika Administratorzy, menedżerowie i ten sam użytkownik co :id mają dostęp

Każdy z tych punktów końcowych będzie obsługiwał uwierzytelnianie, walidację autoryzacji, wykonanie odpowiedniej operacji i ostatecznie zwróci sensowny kod HTTP.

Utworzymy funkcje uwierzytelniania i autoryzacji wymagane do weryfikacji tokena i sprawdzenia, czy oświadczenia zawierają rolę wymaganą do wykonania operacji.

Budowanie API

Aby zbudować API, będziemy potrzebować:

  • Projekt Firebase
  • Zainstalowane firebase-tools

Najpierw zaloguj się do Firebase:

 firebase login

Następnie zainicjuj projekt funkcji:

 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

W tym momencie będziesz mieć folder Funkcje z minimalną konfiguracją do tworzenia funkcji Firebase.

W src/index.ts znajduje się przykład helloWorld , który możesz odkomentować, aby sprawdzić, czy Twoje Funkcje działają. Następnie możesz uruchomić cd functions i uruchomić npm run serve . To polecenie przetranspiluje kod i uruchomi serwer lokalny.

Możesz sprawdzić wyniki na http://localhost:5000/{your-project}/us-central1/helloWorld

Nowa aplikacja Firebase

Zauważ, że funkcja jest widoczna na ścieżce zdefiniowanej jako jej nazwa w 'index.ts: 'helloWorld' .

Tworzenie funkcji Firebase HTTP

Teraz zakodujmy nasze API. Stworzymy funkcję http Firebase i podłączymy ją do ścieżki /api .

Najpierw zainstaluj npm install express .

Na src/index.ts będziemy:

  • Zainicjuj moduł Firebase-Admin SDK za pomocą admin.initializeApp();
  • Ustaw aplikację Express jako moduł obsługi naszego punktu końcowego https api
 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);

Teraz wszystkie żądania skierowane do /api będą obsługiwane przez instancję app .

Następną rzeczą, którą zrobimy, jest skonfigurowanie wystąpienia app do obsługi mechanizmu CORS i dodanie oprogramowania pośredniczącego parsera treści JSON. W ten sposób możemy wysyłać żądania z dowolnego adresu URL i analizować żądania w formacie JSON.

Najpierw zainstalujemy wymagane zależności.

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

I wtedy:

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

Na koniec skonfigurujemy trasy, które app będzie obsługiwać.

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

Firebase Functions pozwala nam ustawić aplikację Express jako procedurę obsługi i dowolną ścieżkę po tej, którą ustawiłeś na functions.https.onRequest(app); — w tym przypadku api — będzie również obsługiwane przez app . Dzięki temu możemy pisać określone punkty końcowe, takie jak api/users , i ustawić procedurę obsługi dla każdego czasownika HTTP, co zrobimy dalej.

Utwórzmy plik src/users/routes-config.ts

Tutaj ustawimy procedurę obsługi create w POST '/users'

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

Teraz utworzymy plik src/users/controller.ts .

W tej funkcji najpierw sprawdzamy, czy wszystkie pola znajdują się w żądaniu treści, a następnie tworzymy użytkownika i ustawiamy niestandardowe oświadczenia.

Po prostu przekazujemy { role } w setCustomUserClaims — pozostałe pola są już ustawione przez Firebase.

Jeśli nie wystąpią żadne błędy, zwracamy kod 201 z uid utworzonego użytkownika.

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

Teraz zabezpieczmy procedurę obsługi, dodając autoryzację. Aby to zrobić, dodamy kilka programów obsługi do naszego punktu końcowego create . Dzięki express.js możesz ustawić łańcuch programów obsługi, które będą wykonywane w kolejności. W ramach procedury obsługi możesz wykonać kod i przekazać go do funkcji obsługi next() lub zwrócić odpowiedź. To, co zrobimy, to najpierw uwierzytelnimy użytkownika, a następnie zweryfikujemy, czy jest on uprawniony do wykonania.

W pliku 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 ); }

Utwórzmy pliki src/auth/authenticated.ts .

W tej funkcji zweryfikujemy obecność tokena nosiciela authorization w nagłówku żądania. Następnie zdekodujemy go za pomocą admin.auth().verifyidToken() i utrwalimy uid , role i adres e- email użytkownika w zmiennej res.locals , której użyjemy później do walidacji autoryzacji.

W przypadku, gdy token jest nieważny, zwracamy klientowi odpowiedź 401:

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

Teraz utwórzmy plik src/auth/authorized.ts .

W tym module obsługi wyodrębniamy informacje o użytkowniku z res.locals , które ustawiliśmy wcześniej i sprawdzamy, czy ma rolę wymaganą do wykonania operacji lub w przypadku, gdy operacja pozwala na wykonanie tego samego użytkownika, sprawdzamy, czy identyfikator w parametrach żądania jest taki sam jak ten w tokenie uwierzytelniania. Jeśli użytkownik nie ma wymaganej roli, zwrócimy 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(); } }

Dzięki tym dwóm metodom będziemy mogli uwierzytelniać żądania i autoryzować je, biorąc pod uwagę role w przychodzącym tokenie. To świetnie, ale ponieważ Firebase nie pozwala nam ustawiać niestandardowych oświadczeń z konsoli projektu, nie będziemy mogli uruchomić żadnego z tych punktów końcowych. Aby to ominąć, możemy utworzyć użytkownika root z Konsoli uwierzytelniania Firebase

Tworzenie użytkownika z Konsoli uwierzytelniania Firebase

I ustaw porównanie wiadomości e-mail w kodzie. Teraz, uruchamiając żądania od tego użytkownika, będziemy mogli wykonać wszystkie operacje.

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

Teraz dodajmy resztę operacji CRUD do src/users/routes-config.ts .

W przypadku operacji pobierania lub aktualizowania pojedynczego użytkownika, w którym wysyłany jest parametr :id , zezwalamy również temu samemu użytkownikowi na wykonanie operacji.

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

Oraz na src/users/controller.ts . W tych operacjach wykorzystujemy pakiet Admin SDK do interakcji z Uwierzytelnianiem Firebase i wykonywania odpowiednich operacji. Tak jak poprzednio przy operacji create , przy każdej operacji zwracamy sensowny kod HTTP.

W przypadku operacji aktualizacji weryfikujemy wszystkie obecne pola i zastępujemy customClaims tymi przesłanymi w żądaniu:

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

Teraz możemy uruchomić funkcję lokalnie. Aby to zrobić, najpierw musisz skonfigurować klucz konta, aby móc lokalnie połączyć się z API auth. Następnie uruchomić:

 npm run serve

Wdróż API

Świetnie! Teraz, gdy mamy już napisany interfejs API oparty na rolach, możemy wdrożyć go w sieci i zacząć z niego korzystać. Wdrażanie za pomocą Firebase jest bardzo łatwe, wystarczy uruchomić firebase deploy . Po zakończeniu wdrażania możemy uzyskać dostęp do naszego interfejsu API pod opublikowanym adresem URL.

Uruchamianie polecenia wdrażania Firebase

Adres URL interfejsu API można sprawdzić pod adresem https://console.firebase.google.com/u/0/project/{your-project}/functions/list.

Adres URL interfejsu API w konsoli Firebase

W moim przypadku jest to [https://us-central1-joaq-lab.cloudfunctions.net/api].

Konsumpcja API

Po wdrożeniu naszego API mamy kilka sposobów, aby z niego korzystać — w tym samouczku omówię, jak z niego korzystać za pośrednictwem Postmana lub aplikacji Angular.

Jeśli wprowadzimy adres URL listy wszystkich użytkowników ( /api/users ) w dowolnej przeglądarce, otrzymamy następujące informacje:

Interfejs API uwierzytelniania Firebase

Powodem tego jest to, że wysyłając żądanie z przeglądarki, wykonujemy żądanie GET bez nagłówków auth. Oznacza to, że nasze API działa zgodnie z oczekiwaniami!

Nasze API jest zabezpieczone za pomocą tokenów – aby wygenerować taki token, musimy wywołać pakiet Client SDK Firebase i zalogować się przy użyciu prawidłowego poświadczenia użytkownika/hasła. Gdy się powiedzie, Firebase odeśle token z powrotem w odpowiedzi, który możemy następnie dodać do nagłówka każdego kolejnego żądania, które chcemy wykonać.

Z aplikacji Angular

W tym samouczku omówię tylko ważne elementy dotyczące korzystania z interfejsu API z aplikacji Angular. Pełne repozytorium jest dostępne tutaj, a jeśli potrzebujesz samouczka krok po kroku, jak utworzyć aplikację Angular i skonfigurować @angular/fire do użycia, możesz sprawdzić ten post.

Tak więc wracając do logowania, będziemy mieli SignInComponent z <form> , aby umożliwić użytkownikowi wprowadzenie nazwy użytkownika i hasła.

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

A na klasie logujemy się za pomocą AngularFireAuth signInWithEmailAndPassword

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

W tym momencie możemy zalogować się do naszego projektu Firebase.

Logowanie przez aplikację Angular

Odpowiedź API podczas logowania z aplikacji Angular

A kiedy sprawdzamy żądania sieciowe w DevTools, widzimy, że Firebase zwraca token po zweryfikowaniu naszego użytkownika i hasła.

Ten token jest tym, którego użyjemy do wysłania żądania naszego nagłówka do zbudowanego przez nas interfejsu API. Jednym ze sposobów dodania tokenu do wszystkich żądań jest użycie HttpInterceptor .

Ten plik pokazuje, jak uzyskać token z AngularFireAuth i dodać go do żądania nagłówka. Następnie udostępniamy plik przechwytywacza w 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.moduł.ts

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

Po ustawieniu przechwytywacza możemy wysyłać żądania do naszego API z httpClient . Na przykład tutaj mamy UsersService , w której wywołujemy listę wszystkich użytkowników, pobieramy użytkownika według jego identyfikatora, tworzymy użytkownika i aktualizujemy użytkownika.

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

Teraz możemy wywołać API, aby pobrać zalogowanego użytkownika według jego identyfikatora i wyświetlić listę wszystkich użytkowników z komponentu w następujący sposób:

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

A oto wynik.

Wszyscy użytkownicy naszej aplikacji Angular

Zwróć uwagę, że jeśli zalogujemy się za pomocą użytkownika z role=user , zostanie wyrenderowana tylko sekcja Ja.

Widok zasobu użytkownika, do którego użytkownik z rolą użytkownik ma dostęp

I dostaniemy 403 na inspektora sieci. Wynika to z ograniczenia, które wcześniej ustawiliśmy w interfejsie API, aby umożliwić tylko „Administratorom” wyświetlanie wszystkich użytkowników.

Błąd 403 w inspektorze sieci

Dodajmy teraz funkcjonalność „utwórz użytkownika” i „edytuj użytkownika”. W tym celu stwórzmy najpierw UserFormComponent i 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) ); } }

Wróćmy do głównego komponentu, dodajmy przyciski do wywoływania tych akcji. W takim przypadku „Edytuj użytkownika” będzie dostępne tylko dla zalogowanego użytkownika. Możesz śmiało dodać funkcję, aby edytować innych użytkowników, jeśli zajdzie taka potrzeba!

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

Od Listonosza

Postman to narzędzie do tworzenia i wysyłania żądań do interfejsów API. W ten sposób możemy zasymulować, że wywołujemy nasze API z dowolnej aplikacji klienckiej lub innej usługi.

Zademonstrujemy sposób wysłania prośby o listę wszystkich użytkowników.

Po otwarciu narzędzia ustawiamy adres URL https://us-central1-{twój-projekt}.cloudfunctions.net/api/users:

URL API załadowany w polu Postman gotowy do uruchomienia jako żądanie GET

Następnie na karcie autoryzacji wybieramy Bearer Token i ustawiamy wartość, którą wcześniej wyodrębniliśmy z Dev Tools.

Ustawianie tokena okaziciela w Postmanie

Treść otrzymanej odpowiedzi

Wniosek

Gratulacje! Udało Ci się przejść przez cały samouczek i teraz nauczyłeś się tworzyć interfejs API oparty na rolach użytkownika w Firebase.

Omówiliśmy również, jak korzystać z aplikacji Angular i Postmana.

Podsumujmy najważniejsze rzeczy:

  1. Firebase umożliwia szybkie rozpoczęcie pracy z interfejsem API uwierzytelniania na poziomie przedsiębiorstwa, który można później rozszerzyć.
  2. Prawie każdy projekt wymaga autoryzacji — jeśli chcesz kontrolować dostęp za pomocą modelu opartego na rolach, Uwierzytelnianie Firebase umożliwia bardzo szybkie rozpoczęcie pracy.
  3. Model oparty na rolach opiera się na weryfikowaniu zasobów wymaganych od użytkowników o określonych rolach w porównaniu z określonymi użytkownikami.
  4. Korzystając z aplikacji Express.js w Firebase Function, możemy utworzyć interfejs API REST i ustawić programy obsługi do uwierzytelniania i autoryzacji żądań.
  5. Korzystając z wbudowanych oświadczeń niestandardowych, możesz utworzyć oparty na rolach interfejs API uwierzytelniania i zabezpieczyć swoją aplikację.

Więcej informacji na temat uwierzytelniania Firebase znajdziesz tutaj. A jeśli chcesz wykorzystać zdefiniowane przez nas role, możesz użyć pomocników @angular/fire.