Como criar uma API baseada em papéis com o Firebase Authentication
Publicados: 2022-03-11Neste tutorial, criaremos uma API REST para gerenciar usuários e funções usando Firebase e Node.js. Além disso, veremos como usar a API para autorizar (ou não) quais usuários podem acessar recursos específicos.
Introdução
Quase todos os aplicativos requerem algum nível de sistema de autorização. Em alguns casos, validar um conjunto de nome de usuário/senha com nossa tabela Users é suficiente, mas muitas vezes precisamos de um modelo de permissões mais refinado para permitir que determinados usuários acessem determinados recursos e os restrinjam de outros. Construir um sistema para suportar o último não é trivial e pode consumir muito tempo. Neste tutorial, aprenderemos a criar uma API de autenticação baseada em função usando o Firebase, o que nos ajudará a começar a usar rapidamente.
Autenticação baseada em função
Nesse modelo de autorização, o acesso é concedido a funções, em vez de usuários específicos, e um usuário pode ter uma ou mais, dependendo de como você projeta seu modelo de permissão. Os recursos, por outro lado, exigem certas funções para permitir que um usuário os execute.
Firebase
Autenticação do Firebase
Em poucas palavras, o Firebase Authentication é um sistema de autenticação extensível baseado em token e fornece integrações prontas para uso com os provedores mais comuns, como Google, Facebook e Twitter, entre outros.
Ele nos permite usar declarações personalizadas que usaremos para criar uma API flexível baseada em função.
Podemos definir qualquer valor JSON nas declarações (por exemplo, { role: 'admin' }
ou { role: 'manager' }
).
Depois de definidas, as declarações personalizadas serão incluídas no token gerado pelo Firebase e podemos ler o valor para controlar o acesso.
Ele também vem com uma cota gratuita muito generosa, que na maioria dos casos será mais que suficiente.
Funções do Firebase
As funções são um serviço de plataforma sem servidor totalmente gerenciado. Só precisamos escrever nosso código em Node.js e implantá-lo. O Firebase cuida do dimensionamento da infraestrutura sob demanda, configuração do servidor e muito mais. No nosso caso, vamos usá-lo para construir nossa API e expô-la via HTTP para a web.
O Firebase nos permite definir aplicativos express.js
como manipuladores para caminhos diferentes. Por exemplo, você pode criar um aplicativo Express e conectá-lo a /mypath
, e todas as solicitações que chegarem a essa rota serão tratadas pelo app
configurado.
No contexto de uma função, você tem acesso a toda a API Firebase Authentication, usando o SDK Admin.
É assim que vamos criar a API do usuário.
O que vamos construir
Então, antes de começarmos, vamos dar uma olhada no que vamos construir. Vamos criar uma API REST com os seguintes endpoints:
Verbo Http | Caminho | Descrição | Autorização |
---|---|---|---|
PEGAR | /Comercial | Lista todos os usuários | Somente administradores e gerentes têm acesso |
PUBLICAR | /Comercial | Cria novo usuário | Somente administradores e gerentes têm acesso |
PEGAR | /usuários/:id | Obtém o usuário :id | Administradores, gerentes e o mesmo usuário que :id têm acesso |
CORREÇÃO | /usuários/:id | Atualiza o usuário :id | Administradores, gerentes e o mesmo usuário que :id têm acesso |
EXCLUIR | /usuários/:id | Exclui o usuário :id | Administradores, gerentes e o mesmo usuário que :id têm acesso |
Cada um desses terminais tratará da autenticação, validará a autorização, executará a operação correspondente e, finalmente, retornará um código HTTP significativo.
Criaremos as funções de autenticação e autorização necessárias para validar o token e verificar se as declarações contêm a função necessária para executar a operação.
Construindo a API
Para construir a API, vamos precisar de:
- Um projeto do Firebase
-
firebase-tools
instaladas
Primeiro, faça login no Firebase:
firebase login
Em seguida, inicialize um projeto do Functions:
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
Neste ponto, você terá uma pasta Functions, com configuração mínima para criar Firebase Functions.
Em src/index.ts
há um exemplo helloWorld
, que você pode descomentar para validar que suas funções funcionam. Então você pode cd functions
e executar npm run serve
. Este comando irá transpilar o código e iniciar o servidor local.
Você pode verificar os resultados em http://localhost:5000/{your-project}/us-central1/helloWorld
Observe que a função é exposta no caminho definido como o nome dela em 'index.ts: 'helloWorld'
.
Como criar uma função HTTP do Firebase
Agora vamos codificar nossa API. Vamos criar uma função http Firebase e ligá-la no caminho /api
.
Primeiro, instale npm install express
.
No src/index.ts
iremos:
- Inicialize o módulo Firebase-admin SDK com
admin.initializeApp();
- Defina um aplicativo Express como o manipulador do nosso endpoint https da
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);
Agora, todas as solicitações que vão para /api
serão tratadas pela instância do app
.
A próxima coisa que faremos é configurar a instância do app
para dar suporte ao CORS e adicionar o middleware do analisador de corpo JSON. Dessa forma, podemos fazer solicitações de qualquer URL e analisar solicitações formatadas em JSON.
Primeiro, instalaremos as dependências necessárias.
npm install --save cors body-parser
npm install --save-dev @types/cors
E então:
//... 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);
Por fim, configuraremos as rotas que o app
manipulará.
//... import { routesConfig } from './users/routes-config'; //… app.use(cors({ origin: true })); routesConfig(app) export const api = functions.https.onRequest(app);
O Firebase Functions nos permite definir um aplicativo Express como manipulador e qualquer caminho após o que você configurou em functions.https.onRequest(app);
—neste caso, api
— também será tratado pelo app
. Isso nos permite escrever endpoints específicos, como api/users
, e definir um manipulador para cada verbo HTTP, o que faremos a seguir.
Vamos criar o arquivo src/users/routes-config.ts
Aqui, vamos definir um manipulador de create
em POST '/users'
import { Application } from "express"; import { create} from "./controller"; export function routesConfig(app: Application) { app.post('/users', create ); }
Agora, vamos criar o arquivo src/users/controller.ts
.
Nesta função, primeiro validamos que todos os campos estão no corpo da solicitação e, em seguida, criamos o usuário e definimos as declarações personalizadas.
Estamos apenas passando { role }
no setCustomUserClaims
— os outros campos já estão definidos pelo Firebase.
Caso não ocorra nenhum erro, retornamos um código 201 com o uid
do usuário criado.
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}` }); }
Agora, vamos proteger o manipulador adicionando autorização. Para fazer isso, adicionaremos alguns manipuladores ao nosso endpoint de create
. Com express.js
, você pode definir uma cadeia de manipuladores que serão executados em ordem. Dentro de um manipulador, você pode executar o código e passá-lo para o manipulador next()
ou retornar uma resposta. O que faremos é primeiro autenticar o usuário e depois validar se ele está autorizado a executar.
No arquivo 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 ); }
Vamos criar os arquivos src/auth/authenticated.ts
.
Nesta função, validaremos a presença do token do portador de authorization
no cabeçalho da solicitação. Em seguida, vamos decodificá-lo com admin.auth().verifyidToken()
e persistir o uid
, a role
e o email
do usuário na variável res.locals
, que usaremos posteriormente para validar a autorização.
Caso o token seja inválido, retornamos uma resposta 401 ao cliente:
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' }); } }
Agora, vamos criar um arquivo src/auth/authorized.ts
.
Neste handler, extraímos as informações do usuário de res.locals
que definimos anteriormente e validamos se ele possui o papel necessário para executar a operação ou caso a operação permita que o mesmo usuário execute, validamos que o ID nos parâmetros de solicitação é igual ao do token de autenticação. Se o usuário não tiver a função necessária, retornaremos um 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(); } }
Com esses dois métodos, poderemos autenticar solicitações e autorizá-las de acordo com a role
no token de entrada. Isso é ótimo, mas como o Firebase não permite definir declarações personalizadas no console do projeto, não poderemos executar nenhum desses endpoints. Para contornar isso, podemos criar um usuário root no Firebase Authentication Console
E defina uma comparação de e-mail no código. Agora, ao disparar requisições deste usuário, poderemos executar todas as operações.
//... const { role, email, uid } = res.locals const { id } = req.params if (email === '[email protected]') return next(); //...
Agora, vamos adicionar o restante das operações CRUD a src/users/routes-config.ts
.
Para operações para obter ou atualizar um único usuário onde o parâmetro :id
é enviado, também permitimos que o mesmo usuário execute a operação.
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 ]); }
E em src/users/controller.ts
. Nessas operações, aproveitamos o SDK admin para interagir com o Firebase Authentication e realizar as respectivas operações. Como fizemos anteriormente na operação de create
, retornamos um código HTTP significativo em cada operação.
Para a operação de atualização, validamos todos os campos presentes e substituímos customClaims
pelos enviados na solicitação:
//.. 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) } } //...
Agora podemos executar a função localmente. Para fazer isso, primeiro você precisa configurar a chave da conta para poder se conectar à API de autenticação localmente. Então corra:

npm run serve
Implantar a API
Excelente! Agora que escrevemos a API baseada em função, podemos implantá-la na Web e começar a usá-la. A implantação com o Firebase é super fácil, só precisamos executar firebase deploy
. Depois que a implantação for concluída, podemos acessar nossa API na URL publicada.
Você pode verificar o URL da API em https://console.firebase.google.com/u/0/project/{your-project}/functions/list.
No meu caso, é [https://us-central1-joaq-lab.cloudfunctions.net/api].
Consumindo a API
Depois que nossa API for implantada, temos várias maneiras de usá-la - neste tutorial, abordarei como usá-la via Postman ou de um aplicativo Angular.
Se inserirmos o URL da lista de todos os usuários ( /api/users
) em qualquer navegador, obteremos o seguinte:
A razão para isso é que ao enviar a solicitação de um navegador, estamos realizando uma solicitação GET sem cabeçalhos de autenticação. Isso significa que nossa API está realmente funcionando conforme o esperado!
Nossa API é protegida por tokens. Para gerar esse token, precisamos chamar o SDK do cliente do Firebase e fazer login com uma credencial de usuário/senha válida. Quando bem-sucedido, o Firebase enviará um token de volta na resposta que podemos adicionar ao cabeçalho de qualquer solicitação a seguir que desejamos executar.
De um aplicativo angular
Neste tutorial, vou apenas examinar as partes importantes para consumir a API de um aplicativo Angular. O repositório completo pode ser acessado aqui, e se você precisar de um tutorial passo a passo sobre como criar um aplicativo Angular e configurar @angular/fire para usar, você pode conferir este post.
Então, de volta ao login, teremos um SignInComponent
com um <form>
para permitir que o usuário insira um nome de usuário e senha.
//... <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> //...
E na classe, signInWithEmailAndPassword
usando o serviço 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) } } //..
Neste ponto, podemos fazer login em nosso projeto Firebase.
E quando inspecionamos as solicitações de rede no DevTools, podemos ver que o Firebase retorna um token após verificar nosso usuário e senha.
Esse token é o que usaremos para enviar a solicitação do nosso cabeçalho para a API que construímos. Uma maneira de adicionar o token a todas as solicitações é usando um HttpInterceptor
.
Este arquivo mostra como obter o token do AngularFireAuth
e adicioná-lo à solicitação do cabeçalho. Em seguida, fornecemos o arquivo interceptor no 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.module.ts
@NgModule({ //.. providers: [ AuthTokenHttpInterceptorProvider ] //... }) export class AppModule { }
Depois que o interceptor estiver definido, podemos fazer solicitações à nossa API de httpClient
. Por exemplo, aqui está um UsersService
onde chamamos a lista de todos os usuários, obtemos o usuário por seu ID, criamos um usuário e atualizamos um usuário.
//… 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) } }
Agora, podemos chamar a API para obter o usuário logado por seu ID e listar todos os usuários de um componente como este:
//... <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)) ) } //...
E aqui está o resultado.
Observe que se entrarmos com um usuário com role=user
, somente a seção Me será renderizada.
E teremos um 403 no inspetor de rede. Isso se deve à restrição que definimos anteriormente na API para permitir que apenas "Admins" listem todos os usuários.
Agora, vamos adicionar as funcionalidades “criar usuário” e “editar usuário”. Para isso, vamos criar primeiro um UserFormComponent
e um 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) ); } }
De volta ao componente principal, vamos adicionar os botões para chamar essas ações. Neste caso, “Editar usuário” estará disponível apenas para o usuário logado. Você pode ir em frente e adicionar a funcionalidade para editar outros usuários se precisar!
//... <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 => { }); }
Do carteiro
Postman é uma ferramenta para construir e fazer requisições a APIs. Dessa forma, podemos simular que estamos chamando nossa API de qualquer aplicativo cliente ou de um serviço diferente.
O que vamos demonstrar é como enviar uma solicitação para listar todos os usuários.
Depois de abrir a ferramenta, definimos a URL https://us-central1-{your-project}.cloudfunctions.net/api/users:
Em seguida, na guia autorização, escolhemos Bearer Token e definimos o valor que extraímos do Dev Tools anteriormente.
Conclusão
Parabéns! Você passou por todo o tutorial e agora aprendeu a criar uma API baseada em função de usuário no Firebase.
Também abordamos como consumi-lo de um aplicativo Angular e Postman.
Vamos recapitular as coisas mais importantes:
- O Firebase permite que você comece a usar rapidamente uma API de autenticação de nível empresarial, que pode ser estendida posteriormente.
- Quase todos os projetos exigem autorização — se você precisar controlar o acesso usando um modelo baseado em função, o Firebase Authentication permite que você comece muito rapidamente.
- O modelo baseado em função depende da validação de recursos que são solicitados de usuários com funções específicas versus usuários específicos.
- Usando um aplicativo Express.js no Firebase Function, podemos criar uma API REST e definir manipuladores para autenticar e autorizar solicitações.
- Aproveitando as declarações personalizadas integradas, você pode criar uma API de autenticação baseada em função e proteger seu aplicativo.
Você pode ler mais sobre a autenticação do Firebase aqui. E se você quiser aproveitar as funções que definimos, você pode usar @angular/fire helpers.