Firebase 인증으로 역할 기반 API를 구축하는 방법
게시 됨: 2022-03-11이 자습서에서는 Firebase 및 Node.js를 사용하여 사용자와 역할을 관리하는 REST API를 빌드합니다. 또한 API를 사용하여 특정 리소스에 액세스할 수 있는 사용자를 승인(또는 승인하지 않음)하는 방법을 알아봅니다.
소개
거의 모든 앱에는 일정 수준의 인증 시스템이 필요합니다. 어떤 경우에는 사용자 테이블로 설정된 사용자 이름/비밀번호의 유효성을 검사하는 것으로 충분하지만 종종 특정 사용자가 특정 리소스에 액세스하고 다른 리소스를 제한할 수 있도록 하려면 보다 세분화된 권한 모델이 필요합니다. 후자를 지원하는 시스템을 구축하는 것은 쉬운 일이 아니며 시간이 많이 소요될 수 있습니다. 이 튜토리얼에서는 Firebase를 사용하여 역할 기반 인증 API를 빌드하는 방법을 배우게 되며, 이를 통해 빠르게 시작하고 실행할 수 있습니다.
역할 기반 인증
이 권한 부여 모델에서는 특정 사용자 대신 역할에 액세스 권한이 부여되며 권한 모델을 설계하는 방법에 따라 사용자는 하나 이상의 역할을 가질 수 있습니다. 반면에 리소스는 사용자가 실행할 수 있도록 특정 역할이 필요합니다.
중포 기지
Firebase 인증
간단히 말해서 Firebase 인증은 확장 가능한 토큰 기반 인증 시스템이며 Google, Facebook, Twitter와 같은 가장 일반적인 공급자와 즉시 통합할 수 있습니다.
이를 통해 유연한 역할 기반 API를 구축하는 데 활용할 사용자 지정 클레임을 사용할 수 있습니다.
모든 JSON 값을 클레임에 설정할 수 있습니다(예 { role: 'admin' }
또는 { role: 'manager' }
).
설정이 완료되면 Firebase에서 생성하는 토큰에 맞춤 클레임이 포함되며 액세스를 제어하기 위해 값을 읽을 수 있습니다.
또한 매우 관대한 무료 할당량이 제공되며 대부분의 경우 충분합니다.
Firebase 함수
기능은 완전히 관리되는 서버리스 플랫폼 서비스입니다. Node.js에 코드를 작성하고 배포하기만 하면 됩니다. Firebase는 주문형 인프라 확장, 서버 구성 등을 처리합니다. 우리의 경우 API를 빌드하고 HTTP를 통해 웹에 노출하는 데 사용할 것입니다.
Firebase를 사용하면 express.js
앱을 다양한 경로의 핸들러로 설정할 수 있습니다. 예를 들어 Express 앱을 만들고 /mypath
에 연결할 수 있으며 이 경로로 들어오는 모든 요청은 구성된 app
에서 처리됩니다.
함수 컨텍스트 내에서 Admin SDK를 사용하여 전체 Firebase 인증 API에 액세스할 수 있습니다.
이것이 우리가 사용자 API를 만드는 방법입니다.
우리가 만들 것
시작하기 전에 무엇을 만들지 살펴보겠습니다. 다음 끝점을 사용하여 REST API를 만들 것입니다.
HTTP 동사 | 길 | 설명 | 권한 부여 |
---|---|---|---|
가져 오기 | /사용자 | 모든 사용자를 나열합니다. | 관리자와 관리자만 액세스할 수 있습니다. |
게시하다 | /사용자 | 새 사용자 생성 | 관리자와 관리자만 액세스할 수 있습니다. |
가져 오기 | /사용자/:ID | :id 사용자를 가져옵니다. | 관리자, 관리자 및 :id와 동일한 사용자에게 액세스 권한이 있습니다. |
반점 | /사용자/:ID | :id 사용자 업데이트 | 관리자, 관리자 및 :id와 동일한 사용자에게 액세스 권한이 있습니다. |
삭제 | /사용자/:ID | :id 사용자를 삭제합니다. | 관리자, 관리자 및 :id와 동일한 사용자에게 액세스 권한이 있습니다. |
이러한 각 끝점은 인증을 처리하고, 권한 부여를 확인하고, 해당 작업을 수행하고, 마지막으로 의미 있는 HTTP 코드를 반환합니다.
토큰의 유효성을 검사하는 데 필요한 인증 및 권한 부여 기능을 만들고 클레임에 작업을 실행하는 데 필요한 역할이 포함되어 있는지 확인합니다.
API 빌드
API를 빌드하려면 다음이 필요합니다.
- Firebase 프로젝트
-
firebase-tools
설치
먼저 Firebase에 로그인합니다.
firebase login
다음으로 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
이 시점에서 Firebase 함수를 생성하기 위한 최소한의 설정이 포함된 Functions 폴더가 생깁니다.
src/index.ts
에는 함수가 작동하는지 확인하기 위해 주석을 제거할 수 있는 helloWorld
예제가 있습니다. 그런 다음 cd functions
하고 npm run serve
를 실행할 수 있습니다. 이 명령은 코드를 변환하고 로컬 서버를 시작합니다.
결과는 http://localhost:5000/{your-project}/us-central1/helloWorld에서 확인할 수 있습니다.
함수는 'index.ts: 'helloWorld'
에서 이름으로 정의된 경로에 노출되어 있습니다.
Firebase HTTP 함수 만들기
이제 API를 코딩해 보겠습니다. http Firebase 함수를 만들고 /api
경로에 연결합니다.
먼저 npm install express
를 설치합니다.
src/index.ts
우리는:
-
admin.initializeApp();
- Express 앱을
api
https 엔드포인트의 핸들러로 설정합니다.
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);
이제 /api
로 가는 모든 요청은 app
인스턴스에서 처리됩니다.
다음으로 할 일은 app
인스턴스를 구성하여 CORS를 지원하고 JSON 본문 파서 미들웨어를 추가하는 것입니다. 이렇게 하면 모든 URL에서 요청을 만들고 JSON 형식의 요청을 구문 분석할 수 있습니다.
먼저 필수 종속성을 설치합니다.
npm install --save cors body-parser
npm install --save-dev @types/cors
그런 다음:
//... 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);
마지막으로 app
이 처리할 경로를 구성합니다.
//... import { routesConfig } from './users/routes-config'; //… app.use(cors({ origin: true })); routesConfig(app) export const api = functions.https.onRequest(app);
Firebase Functions를 사용하면 Express 앱을 핸들러로 설정할 수 있으며, functions.https.onRequest(app);
—이 경우 api
—도 app
에서 처리됩니다. 이를 통해 api/users
와 같은 특정 끝점을 작성하고 다음에 수행할 각 HTTP 동사에 대한 처리기를 설정할 수 있습니다.
src/users/routes-config.ts
파일을 생성합시다.
여기에서는 POST '/users'
에서 create
핸들러를 설정합니다.
import { Application } from "express"; import { create} from "./controller"; export function routesConfig(app: Application) { app.post('/users', create ); }
이제 src/users/controller.ts
파일을 생성하겠습니다.
이 함수에서는 먼저 모든 필드가 본문 요청에 있는지 확인한 다음 사용자를 만들고 사용자 지정 클레임을 설정합니다.
setCustomUserClaims
에서 { role }
을 전달하는 중입니다. 다른 필드는 Firebase에서 이미 설정되어 있습니다.
오류가 발생하지 않으면 생성된 사용자의 uid
와 함께 201 코드를 반환합니다.
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}` }); }
이제 인증을 추가하여 핸들러를 보호해 보겠습니다. 이를 위해 create
엔드포인트에 두 개의 핸들러를 추가합니다. express.js
를 사용하면 순서대로 실행될 핸들러 체인을 설정할 수 있습니다. 핸들러 내에서 코드를 실행하고 next()
핸들러에 전달하거나 응답을 반환할 수 있습니다. 우리가 할 일은 먼저 사용자를 인증한 다음 실행할 권한이 있는지 확인하는 것입니다.
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 ); }
src/auth/authenticated.ts
파일을 생성해 보겠습니다.
이 함수에서 요청 헤더에 authorization
전달자 토큰이 있는지 확인합니다. 그런 다음 admin.auth().verifyidToken()
으로 디코딩하고 사용자의 uid
, role
및 email
을 res.locals
변수에 유지합니다. 이 변수는 나중에 인증을 확인하는 데 사용할 것입니다.
토큰이 유효하지 않은 경우 클라이언트에 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' }); } }
이제 src/auth/authorized.ts
파일을 생성해 보겠습니다.
이 핸들러에서는 이전에 설정한 res.locals
에서 사용자 정보를 추출하고 작업을 실행하는 데 필요한 역할이 있는지 확인하거나 작업에서 동일한 사용자가 실행할 수 있도록 허용하는 경우 요청 매개변수의 ID가 인증 토큰에 있는 것과 동일합니다. 사용자에게 필요한 역할이 없으면 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(); } }
이 두 가지 방법을 사용하여 요청을 인증하고 들어오는 토큰의 role
에 따라 권한을 부여할 수 있습니다. 훌륭하지만 Firebase를 사용하면 프로젝트 콘솔에서 사용자 지정 클레임을 설정할 수 없으므로 이러한 끝점을 실행할 수 없습니다. 이를 우회하기 위해 Firebase 인증 콘솔에서 루트 사용자를 만들 수 있습니다.
그리고 코드에서 이메일 비교를 설정합니다. 이제 이 사용자의 요청을 실행할 때 모든 작업을 실행할 수 있습니다.
//... const { role, email, uid } = res.locals const { id } = req.params if (email === '[email protected]') return next(); //...
이제 나머지 CRUD 작업을 src/users/routes-config.ts
에 추가해 보겠습니다.
:id
param이 전송된 단일 사용자를 가져오거나 업데이트하는 작업의 경우 동일한 사용자가 작업을 실행할 수도 있습니다.
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 ]); }
그리고 src/users/controller.ts
. 이러한 작업에서 admin SDK를 활용하여 Firebase 인증과 상호 작용하고 해당 작업을 수행합니다. 이전에 create
작업에서 했던 것처럼 각 작업에서 의미 있는 HTTP 코드를 반환합니다.
업데이트 작업을 위해 존재하는 모든 필드의 유효성을 검사하고 요청에서 보낸 필드로 customClaims
를 재정의합니다.
//.. 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) } } //...
이제 로컬에서 함수를 실행할 수 있습니다. 그렇게 하려면 먼저 로컬에서 인증 API에 연결할 수 있도록 계정 키를 설정해야 합니다. 그런 다음 다음을 실행합니다.
npm run serve
API 배포
엄청난! 이제 역할 기반 API를 작성했으므로 웹에 배포하고 사용을 시작할 수 있습니다. Firebase로 배포하는 것은 매우 쉽습니다. firebase deploy
를 실행하기만 하면 됩니다. 배포가 완료되면 게시된 URL에서 API에 액세스할 수 있습니다.
API URL은 https://console.firebase.google.com/u/0/project/{your-project}/functions/list에서 확인할 수 있습니다.

제 경우에는 [https://us-central1-joaq-lab.cloudfunctions.net/api]입니다.
API 사용
API가 배포되면 이를 사용할 수 있는 여러 가지 방법이 있습니다. 이 자습서에서는 Postman 또는 Angular 앱을 통해 API를 사용하는 방법을 다룰 것입니다.
모든 브라우저에서 List All Users URL( /api/users
)을 입력하면 다음이 표시됩니다.
그 이유는 브라우저에서 요청을 보낼 때 인증 헤더 없이 GET 요청을 수행하기 때문입니다. 이것은 우리 API가 실제로 예상대로 작동하고 있음을 의미합니다!
API는 토큰을 통해 보호됩니다. 이러한 토큰을 생성하려면 Firebase의 클라이언트 SDK를 호출하고 유효한 사용자/비밀번호 자격 증명으로 로그인해야 합니다. 성공하면 Firebase는 응답으로 토큰을 다시 보낸 다음 수행하려는 다음 요청의 헤더에 추가할 수 있습니다.
Angular 앱에서
이 튜토리얼에서는 Angular 앱에서 API를 사용하는 중요한 부분을 살펴보겠습니다. 전체 저장소는 여기에서 액세스할 수 있으며 Angular 앱을 만들고 사용하도록 @angular/fire를 구성하는 방법에 대한 단계별 자습서가 필요한 경우 이 게시물을 확인할 수 있습니다.
따라서 로그인으로 돌아가서 사용자가 사용자 이름과 암호를 입력할 수 있도록 <form>
이 있는 SignInComponent
가 있습니다.
//... <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> //...
그리고 클래스에서 signInWithEmailAndPassword
서비스를 사용하여 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) } } //..
이 시점에서 Firebase 프로젝트에 로그인할 수 있습니다.
그리고 DevTools에서 네트워크 요청을 검사할 때 Firebase가 사용자와 비밀번호를 확인한 후 토큰을 반환하는 것을 볼 수 있습니다.
이 토큰은 헤더의 요청을 우리가 구축한 API로 보내는 데 사용할 토큰입니다. 모든 요청에 토큰을 추가하는 한 가지 방법은 HttpInterceptor
를 사용하는 것입니다.
이 파일은 AngularFireAuth
에서 토큰을 가져와 헤더의 요청에 추가하는 방법을 보여줍니다. 그런 다음 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 { }
인터셉터가 설정되면 httpClient
에서 API에 요청할 수 있습니다. 예를 들어 다음은 모든 사용자 목록을 호출하고 ID로 사용자를 가져오고 사용자를 만들고 사용자를 업데이트하는 UsersService
입니다.
//… 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) } }
이제 API를 호출하여 로그인한 사용자를 ID로 가져오고 다음과 같이 구성 요소의 모든 사용자를 나열할 수 있습니다.
//... <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)) ) } //...
그리고 여기 결과가 있습니다.
role=user
인 사용자로 로그인하면 Me 섹션만 렌더링됩니다.
그리고 우리는 네트워크 검사기에서 403을 얻을 것입니다. 이는 "관리자"만 모든 사용자를 나열할 수 있도록 API에 대해 이전에 설정한 제한 때문입니다.
이제 "사용자 생성" 및 "사용자 편집" 기능을 추가해 보겠습니다. 이를 위해 먼저 UserFormComponent
와 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) ); } }
기본 구성 요소로 돌아가서 해당 작업을 호출하는 버튼을 추가해 보겠습니다. 이 경우 "사용자 편집"은 로그인한 사용자만 사용할 수 있습니다. 필요한 경우 다른 사용자를 편집하는 기능을 추가할 수 있습니다!
//... <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 => { }); }
우편 배달부에서
Postman은 API를 빌드하고 요청하는 도구입니다. 이런 식으로 클라이언트 앱이나 다른 서비스에서 API를 호출하는 것을 시뮬레이션할 수 있습니다.
우리가 시연할 것은 모든 사용자를 나열하는 요청을 보내는 방법입니다.
도구를 열면 URL https://us-central1-{your-project}.cloudfunctions.net/api/users를 설정합니다.
다음으로 탭 인증에서 Bearer Token을 선택하고 이전에 Dev Tools에서 추출한 값을 설정합니다.
결론
축하합니다! 전체 자습서를 완료했으며 이제 Firebase에서 사용자 역할 기반 API를 만드는 방법을 배웠습니다.
Angular 앱과 Postman에서 이를 사용하는 방법도 다루었습니다.
가장 중요한 사항을 요약해 보겠습니다.
- Firebase를 사용하면 나중에 확장할 수 있는 엔터프라이즈 수준 인증 API를 빠르게 시작하고 실행할 수 있습니다.
- 거의 모든 프로젝트에는 승인이 필요합니다. 역할 기반 모델을 사용하여 액세스를 제어해야 하는 경우 Firebase 인증을 사용하면 매우 빠르게 시작할 수 있습니다.
- 역할 기반 모델은 특정 역할을 가진 사용자와 특정 사용자가 요청한 리소스의 유효성을 검사하는 데 의존합니다.
- Firebase Function에서 Express.js 앱을 사용하여 REST API를 만들고 핸들러를 설정하여 요청을 인증하고 승인할 수 있습니다.
- 기본 제공 사용자 지정 클레임을 활용하여 역할 기반 인증 API를 만들고 앱을 보호할 수 있습니다.
Firebase 인증에 대한 자세한 내용은 여기에서 확인할 수 있습니다. 그리고 우리가 정의한 역할을 활용하려면 @angular/fire 도우미를 사용할 수 있습니다.