Jak zbudować interfejs API oparty na rolach za pomocą uwierzytelniania Firebase
Opublikowany: 2022-03-11W 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.
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 loginNastę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? YesW 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
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/corsI 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
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 serveWdróż 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.
Adres URL interfejsu API można sprawdzić pod adresem https://console.firebase.google.com/u/0/project/{your-project}/functions/list.
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:
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.
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.
Zwróć uwagę, że jeśli zalogujemy się za pomocą użytkownika z role=user , zostanie wyrenderowana tylko sekcja Ja.
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.
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">×</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:
Następnie na karcie autoryzacji wybieramy Bearer Token i ustawiamy wartość, którą wcześniej wyodrębniliśmy z Dev Tools.
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:
- Firebase umożliwia szybkie rozpoczęcie pracy z interfejsem API uwierzytelniania na poziomie przedsiębiorstwa, który można później rozszerzyć.
- 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.
- 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.
- 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ń.
- 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.
