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