在NestJS應用程式中使用Firebase認證
簡介
在這篇文章中,我們將建立一個小專案,將Firebase認證整合到NestJS應用程式中。
認證是任何應用程式的一個重要組成部分,但從頭開始設定可能會有很大的壓力。這是Firebase通過其認證產品解決的一個問題。
Firebase包括一系列的產品和解決方案,使應用開發更容易。Firebase提供的一些服務包括資料庫、認證、分析和託管,等等。Firebase可以通過firebase-adminnpm模組被整合到NodeJS應用中。
NestJS幫助你使用TypeScript建立伺服器端的NodeJS應用程式。該框架在npm上每週有超過60萬次的下載,在GitHub上有35K顆星,是一個非常受歡迎的框架。它有一個Angular型別的架構,具有控制器和模組等功能。NestJS在引擎蓋下使用Express,儘管它也可以被配置為使用Fastify。
該專案
我們將建立一個簡單的應用程式,只允許認證的使用者訪問一個資源。使用者可以通過Firebase客戶端的登入和註冊來進行認證。在認證時,會向用戶提供一個JSON Web Token(JWT),然後將其與隨後對受限資源的請求一起傳送。所提供的JWT會在伺服器端使用firebase-admin
SDK進行驗證,並根據JWT的有效性允許或拒絕訪問。
開始使用
首先,讓我們建立一個Firebase應用程式。這將為我們提供一些配置,我們將在以後的NestJS應用程式中使用。你可以通過Firebase控制檯做到這一點。點選新增專案,然後命名你的專案。我們在這個專案中不需要谷歌分析,所以你不必啟用它。然後你可以點選建立專案。
一旦你的應用程式被建立,點選專案概覽旁邊的設定圖示,選擇專案 設定。在服務賬戶標籤下,生成一個新的私鑰。這應該會下載一個帶有一些證書的JSON檔案,我們會用它來在伺服器(NestJS)端初始化我們的Firebase Admin SDK。
在同一個專案設定選單中,在常規標籤下,滾動到你的應用程式,向Firebase註冊你的應用程式(如果你已經在Firebase註冊了一個應用程式,點選新增應用程式按鈕)。
我們的應用程式是基於網路的,所以選擇 ``圖示。接下來,給你的應用程式一個暱稱。你不需要選擇Firebase託管,除非你打算這樣做。
你會得到一些指令碼的連結以及Firebase的配置,這些配置是你的應用程式正常執行所需要的。把這些內容複製到一個你可以輕鬆訪問的地方,因為以後會需要它。
之後,點選認證(位於Build側邊欄下),在登入方式選單下,啟用電子郵件/密碼。我們將用使用者的電子郵件和密碼進行認證。
初始化你的NestJS應用程式
接下來,我們將全域性安裝Nest CLI包。這將為我們提供一些命令,其中之一是nest
命令,我們可以用它來啟動一個新的NestJS應用程式。
``` npm i -g @nestjs/cli //install nest cli package globally
nest new firebase-auth-project //create a new nestjs project in a folder named firebase-auth-project
```
建立一個新專案的安裝過程可能需要一點時間,因為所有需要的依賴都需要安裝。新專案應該有git初始化,一些資料夾自動新增到.gitignore
。將*/**/firebase.config.json
新增到.gitignore
。
使用npm run start:dev
命令在開發中啟動你的應用程式。NestJS預設在3000埠執行,當檔案被儲存時,伺服器會自動重新啟動。每當你啟動應用程式時,你的TypeScript檔案會被編譯成dist
資料夾中的純JavaScript。
我們將使用伺服器上的Handlebars檔案。要做到這一點,我們需要hbs
模組,可以用以下命令來安裝。
``` npm i hbs npm i @types/hbs
```
Handlebars是一個模板引擎,幫助我們編寫可重複使用的動態HTML。你可以在這裡閱讀更多關於模板引擎的資訊。
你現在可以修改你的main.ts
檔案,看起來像這樣。
``` import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { join } from 'path'; import { Logger } from '@nestjs/common'; import { AppModule } from './app.module'; import * as hbs from 'hbs';
async function bootstrap() {
const app = await NestFactory.create
bootstrap();
```
你的檔案中每一行的末尾都可能有一個
Delete`␍`
的錯誤,特別是如果你執行的是Windows。這是因為在Windows中,行末序列由CR(carriage-return character)
和換行符,或LF(linefeed character)
表示,而git只使用換行符LF
。執行npm run lint
應該可以解決這個問題,或者你可以在你的程式碼編輯器中手動設定行結束序列為LF
。
app.set('view options', { layout: 'main' });
表示一個main.hbs
檔案將作為我們hbs
檔案的佈局。
在這個專案中,我們將使用幾個軟體包,所以在進一步討論之前,讓我們把它們全部安裝好。
``` npm i @nestjs/passport class-transformer firebase-admin passport passport-firebase-jwt
```
Passport是一個易於使用且非常流行的NodeJS認證庫,並通過@nestjs/passport模組與NestJS很好地合作,提供一個強大的認證系統。
建立路由和hbs
檔案
讓我們來建立我們的第一個路由。在app.controller.ts
檔案中,新增以下程式碼。
``` import { Controller, Get, Render } from '@nestjs/common'; import { AppService } from './app.service';
@Controller('') export class AppController { constructor(private readonly appService: AppService) {} @Get('login') @Render('login') login() { return; }
@Get('signup') @Render('signup') signup() { return; } }
```
這表明,當我們向/login
路由傳送一個GET
請求時,login.hbs
檔案應該為我們呈現,同時也是註冊路由。現在讓我們來建立這些hbs
檔案。
在你專案的根目錄下,建立public
和views
資料夾。你的資料夾結構應該看起來有點像這樣。
``` ├──-public ├──-src ├───test ├───views
```
記住,我們已經指出main.hbs
是我們的佈局檔案,所以在檢視資料夾內,建立main.hbs
檔案並新增以下程式碼。
```
{{{body}}}`; } return html; }; ``` `quotes`,`error`, 和`displayQuotes` 是將被`login.js` 和`signup.js` 指令碼使用的變數,所以你的`main.js` 檔案在其他兩個檔案之前被匯入是很重要的。反過來,`main.js` 可以訪問`firebase` 變數,因為 Firebase 指令碼首先被包含在`main.hbs` 檔案中。 現在,為了處理使用者的註冊,在`signup.js` 中新增這個。 ``` const signupForm = document.getElementById('signup-form'); const emailField = document.getElementById('email'); const passwordField = document.getElementById('password'); signupForm.addEventListener('submit', (e) => { e.preventDefault(); const email = emailField.value; const password = passwordField.value; firebase .auth() .createUserWithEmailAndPassword(email, password) .then(({ user }) => { return user.getIdToken().then((idToken) => { return fetch('/resources', { method: 'GET', headers: { Accept: 'application/json', Authorization: `Bearer ${idToken}`, }, }) .then((resp) => resp.json()) .then((resp) => { const html = displayQuotes(resp); quotes.innerHTML = html; document.title = 'quotes'; window.history.pushState( { html, pageTitle: 'quotes' }, '', '/resources', ); signupForm.style.display = 'none'; quotes.classList.remove('d-none'); }) .catch((err) => { console.error(err.message); error.innerHTML = err.message; }); }); }) .catch((err) => { console.error(err.message); error.innerHTML = err.message; }); }); ``` 並在`login.js` 中登入。 ``` const loginForm = document.getElementById('login-form'); const emailField = document.getElementById('email'); const passwordField = document.getElementById('password'); loginForm.addEventListener('submit', (e) => { e.preventDefault(); const email = emailField.value; const password = passwordField.value; firebase .auth() .signInWithEmailAndPassword(email, password) .then(({ user }) => { return user.getIdToken().then((idToken) => { return fetch('/resources', { method: 'GET', headers: { Accept: 'application/json', Authorization: `Bearer ${idToken}`, }, }) .then((resp) => resp.json()) .then((resp) => { const html = displayQuotes(resp); quotes.innerHTML = html; document.title = 'quotes'; window.history.pushState( { html, pageTitle: 'quotes' }, '', '/resources', ); loginForm.style.display = 'none'; quotes.classList.remove('d-none'); }) .catch((err) => { console.error(err.message); error.innerHTML = err.message; }); }); }) .catch((err) => { console.error(err.message); error.innerHTML = err.message; }); }); ``` Firebase-admin -------------- 雖然使用者現在可以註冊並登入到我們的應用程式,但我們的`resources` 路徑仍然是開放的,任何人都可以訪問。記住,我們在我們的NestJS應用程式中安裝了`firebase-admin` 。正如我前面提到的,這個包將幫助驗證從客戶端傳送的JWT令牌,然後允許或拒絕使用者訪問路由。 在`src` 資料夾中,建立一個名為`firebase` 的資料夾。這將包含我們所有的Firebase設定。在`firebase` 資料夾中,建立一個名為`firebase.config.json` 的檔案。這將包含你在服務賬戶標籤下生成私鑰時下載的JSON檔案的值。 ``` { "type": "service_account", "project_id": "", "private_key_id": "", "private_key": "", "client_email": "", "client_id": "", "auth_uri": "", "token_uri": "", "auth_provider_x509_cert_url": "", "client_x509_cert_url": "" } ``` 保持這些值的私密性是很重要的,因為其中有些值是非常敏感的。 接下來,我們要為Firebase建立一個Passport策略。策略是Passport中特定服務(這裡是指Firebase)的認證機制。在`firebase` 資料夾中建立一個`firebase-auth.strategy.ts` 檔案,並新增以下程式碼。 ``` import { PassportStrategy } from '@nestjs/passport'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Strategy, ExtractJwt } from 'passport-firebase-jwt'; import * as firebaseConfig from './firebase.config.json'; import * as firebase from 'firebase-admin'; const firebase_params = { type: firebaseConfig.type, projectId: firebaseConfig.project_id, privateKeyId: firebaseConfig.private_key_id, privateKey: firebaseConfig.private_key, clientEmail: firebaseConfig.client_email, clientId: firebaseConfig.client_id, authUri: firebaseConfig.auth_uri, tokenUri: firebaseConfig.token_uri, authProviderX509CertUrl: firebaseConfig.auth_provider_x509_cert_url, clientC509CertUrl: firebaseConfig.client_x509_cert_url, }; @Injectable() export class FirebaseAuthStrategy extends PassportStrategy( Strategy, 'firebase-auth', ) { private defaultApp: any; constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), }); this.defaultApp = firebase.initializeApp({ credential: firebase.credential.cert(firebase_params), }); } async validate(token: string) { const firebaseUser: any = await this.defaultApp .auth() .verifyIdToken(token, true) .catch((err) => { console.log(err); throw new UnauthorizedException(err.message); }); if (!firebaseUser) { throw new UnauthorizedException(); } return firebaseUser; } } ``` 這裡發生了什麼?JWT被作為承載令牌從請求頭中提取出來,我們的Firebase應用程式被用來驗證該令牌。如果令牌有效,就會返回結果,否則就會拒絕使用者的請求,並丟擲一個未經授權的異常。 > 如果你在匯入Firebase配置時遇到ESLint錯誤,請在你的`tsconfig.json` 檔案中新增這個:`"resolveJsonModule": true` 。 整合策略 ---- 現在,我們的認證策略是一個獨立的函式,這並沒有什麼幫助。我們可以讓它成為中介軟體,並將其整合到需要認證的端點中,但NestJS有一種更簡單、更好的處理認證的方式,叫做[Guards](http://docs.nestjs.com/guards)。我們將建立一個衛士來利用我們的Firebase策略,並通過一個簡單的裝飾器,將其包裹在需要認證的路由中。 建立一個名為`firebase-auth.guard.ts` 的檔案,並在其中新增以下程式碼。 ``` import { ExecutionContext, Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Reflector } from '@nestjs/core'; @Injectable() export class FirebaseAuthGuard extends AuthGuard('firebase-auth') { constructor(private reflector: Reflector) { super(); } canActivate(context: ExecutionContext) { const isPublic = this.reflector.getAllAndOverride${quote.quote}.
${quote.character}
- Flutter中無狀態和有狀態部件之間的區別
- Bootstrap 5.2.0的詳細介紹
- 使用處理器API的Kafka流有狀態攝取
- 如何在Go中使用指標
- Kotlin中的正則表示式指南
- Kotlin中的正則表示式指南
- 【譯】瀏覽器拓展:SVG Gobbler
- 【譯】PostgreSQL
- 使用BLoC設計模式的Flutter狀態管理
- 為什麼要將Tailwind CSS與React Native一起使用?
- Laravel和Docker:使用Laravel Sail的指南
- 使用React Native圖表套件來實現資料的視覺化
- Firebase和Fauna。比較前端開發的資料庫工具
- 在NestJS應用程式中使用Firebase認證
- 使用Angular DataTables來構建功能豐富的表格
- Iced.rs教程。如何建立一個簡單的Rust前臺網路應用程式
- 用React前端構建一個多租戶的Amplify應用
- 使用多層結構優化React應用程式
- 如何用CapRover建立你自己的PaaS
- JavaScript測試。需要學習的9個最佳實踐