使用 Firebase 身份验证保护您的 express/Node.js API
很多应用,无论是移动应用还是网页应用,都需要某种形式的身份验证。如果你开发过各种应用,就会发现处理身份验证是一项相当重复且枯燥的任务,因此我喜欢使用像 Auth0 或 Firebase 这样的外部服务来简化身份验证流程。这些服务还可以处理社交身份验证,从而节省大量代码。我们只需要专注于集成即可。
本文将介绍如何使用 Firebase 来保护我们的 API,确保只有授权用户才能访问我们的资源。一种常见的 API 安全保护方法是使用 JWT 令牌,该令牌在用户提供有效的身份验证凭据后生成,并在每次请求时进行验证。这与我们将要使用 Firebase 实现的功能非常相似。我们将利用 Firebase 来处理令牌的生成和验证。
请注意,本文并非旨在教您如何创建/启动 Express 服务器。如果您不熟悉 Node.js 或 Express,建议您在阅读本文之前先了解相关知识。
是时候深入了解一下代码了。
访问您的Firebase 控制台,如果您尚未创建新项目,请创建一个新项目。
服务器端
服务器端方面,我们将使用 Firebase 管理 SDK,因为它更适合我们想要实现的目标。
使用以下命令在服务器上安装管理 SDK:
npm i firebase-admin
为了验证您是从受信任的环境调用 API,Google 建议您为项目生成并下载服务帐号密钥,并将其添加到环境变量中的某个路径。因此,请前往 Google 控制台,生成服务帐号密钥,下载它(最好是 JSON 格式),并将其位置添加到运行服务器的环境变量中的某个路径(GOOGLE_APPLICATION_CREDENTIALS)中。
exports GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-file.json
点击这里查看更多相关信息。
现在我们可以在项目中创建一个服务,在该服务中,我们将使用我们的凭据初始化 SDK 并将其导出。
import * as admin from 'firebase-admin';
admin.initializeApp(
credential: admin.credential.applicationDefault(),
databaseURL: 'https://<DATABASE_NAME>.firebaseio.com'
);
export default admin
接下来,我们将编写处理创建新用户的逻辑。我们可以将此逻辑放在身份验证控制器中,或者任何您认为合适的地方。
import admin from './firebase-service';
export const createUser = async (req, res) => {
const {
email,
phoneNumber,
password,
firstName,
lastName,
photoUrl
} = req.body;
const user = await admin.auth().createUser({
email,
phoneNumber,
password,
displayName: `${firstName} ${lastName}`,
photoURL: photoUrl
});
return res.send(user);
}
现在创建用户的逻辑已经实现。接下来,我们需要确保所有传入的请求都来自已认证的用户。我们可以通过创建中间件来保护那些我们希望保持私密的路由,从而实现这一点。
我们将创建一个身份验证中间件,以确保请求标头中存在有效的 Firebase 令牌。
import admin from './firebase-service';
const getAuthToken = (req, res, next) => {
if (
req.headers.authorization &&
req.headers.authorization.split(' ')[0] === 'Bearer'
) {
req.authToken = req.headers.authorization.split(' ')[1];
} else {
req.authToken = null;
}
next();
};
export const checkIfAuthenticated = (req, res, next) => {
getAuthToken(req, res, async () => {
try {
const { authToken } = req;
const userInfo = await admin
.auth()
.verifyIdToken(authToken);
req.authId = userInfo.uid;
return next();
} catch (e) {
return res
.status(401)
.send({ error: 'You are not authorized to make this request' });
}
});
};
有了这套中间件,用户每次尝试在未经过身份验证的情况下访问私有资源时,都会收到“未授权”错误。
现在我们已经创建了中间件,让我们用它来保护我们的私有路由。
import {Router} from 'express';
import {createUser} from './controllers/authcontroller';
import {checkIfAuthenticated} from './middlewares/auth-middleware';
import {articles} from from './data';
const router = Router();
router.post('/auth/signup', createUser);
router.get('/articles', checkIfAuthenticated, async (_, res) => {
return res.send(articles);
});
export default router;
上面的代码定义了两条路由。一条用于创建用户,另一条仅当用户通过身份验证后才用于获取文章。现在让我们转到客户端,看看如何使用这个 API。
客户端
我们可以使用任何用于 Web 或移动应用的 JavaScript 客户端库或框架来调用我们的 API,因此我不会具体说明任何库或框架,而是重点介绍 Firebase JavaScript SDK。尽管不同的 JavaScript 库/框架在 SDK 中可能存在一些差异,但它们的 API 与官方 Web SDK 仍然非常相似。
因此,我们在客户端安装 Firebase。
npm i firebase
注意:您的平台可能需要不同的 SDK 和安装方法,例如 angular-fire 和 react-native-firebase。
为了保持代码的简洁性,我们还可以在客户端创建一个服务,用于使用我们的配置初始化 Firebase。
import * as firebase from 'firebase/app';
import 'firebase/auth';
const config = {
apiKey: "api-key",
authDomain: "project-id.firebaseapp.com",
databaseURL: "https://project-id.firebaseio.com",
projectId: "project-id",
storageBucket: "project-id.appspot.com",
messagingSenderId: "sender-id",
appID: "app-id",
}
firebase.initializeApp(config);
export const auth = firebase.auth
export default firebase;
您的凭据可在Firebase 控制台中找到。如果您未在 Web 端使用 JavaScript,则应查看如何在您的特定平台上初始化 Firebase。
我们将创建一个身份验证服务,用于调用注册端点和用户登录。
import axios from 'axios';
import {auth} from './firebase-service';
export const createUserAccount = (data) => {
return axios.post('https://your-api-url/auth/signup', data)
.then(res => res.data)
}
export const loginUser = (email, password) => {
return auth().signInWithEmailAndPassword(email, password);
}
我们已经定义了创建用户并将其登录到我们应用的逻辑。接下来,我们可以通过 Firebase 检查用户是否已登录。
firebase.auth().onAuthStateChanged(user => {
if (user) {
return user;
}
});
现在我们已经完成了注册和登录流程,接下来让我们在客户端生成一个令牌,用于服务器端的身份验证。这只需一行代码即可轻松完成。没错!你没听错,只需一行代码。
const token = await firebase.auth.currentUser.getIdToken();
您可以像上面那样在异步函数中使用它,或者解析 Promise 来获取令牌值。我们将向 API 发送请求,并将令牌附加到请求头中,以访问文章资源。
import {auth} from './firebase-service';
export const getArticles = async () => {
const token = await auth.currentUser.getIdToken();
return axios.get('https://your-api-url/articles', {headers:
{ authorization: `Bearer ${token}` }})
.then(res => res.data);
}
我们只需将 Firebase 令牌添加到授权标头中即可。服务器端会提取该令牌并用于验证用户身份。这一切都将由我们之前创建的中间件处理。
用户角色
用户身份验证中一个非常重要的环节是角色管理。如果我们想要设置不同的授权级别,并限制具有特定角色的用户访问某些资源,该怎么办?使用 Firebase 身份验证也很容易实现这一点。
我们将管理服务器上的角色,以下是具体操作方法。
import admin from './firebase-service';
export const makeUserAdmin = async (req, res) => {
const {userId} = req.body; // userId is the firebase uid for the user
await admin.auth().setCustomUserClaims(userId, {admin: true});
return res.send({message: 'Success'})
}
既然我们已经可以为用户分配角色,那么如何检查用户是否拥有特定角色呢?很简单,当我们在中间件中验证用户令牌时,就可以轻松地从返回的数据中获取此信息。我们将添加一个中间件来检查用户是否拥有管理员角色。
import admin from './firebase-service';
const getAuthToken = (req, res, next) => {
if (
req.headers.authorization &&
req.headers.authorization.split(' ')[0] === 'Bearer'
) {
req.authToken = req.headers.authorization.split(' ')[1];
} else {
req.authToken = null;
}
next();
};
export const checkIfAdmin = (req, res, next) => {
getAuthToken(req, res, async () => {
try {
const { authToken } = req;
const userInfo = await admin
.auth()
.verifyIdToken(authToken);
if (userInfo.admin === true) {
req.authId = userInfo.uid;
return next();
}
throw new Error('unauthorized')
} catch (e) {
return res
.status(401)
.send({ error: 'You are not authorized to make this request' });
}
});
};
现在我们可以使用此中间件来保护我们的管理资源。以下是我们更新后的路由。
import {Router} from 'express';
import {createUser} from './controllers/authcontroller';
import {checkIfAuthenticated, checkifAdmin} from './middlewares/auth-middleware';
import {articles} from from './data';
import {records} from './data/admin';
const router = Router();
router.post('/auth/signup', createUser);
router.get('/stories', checkIfAuthenticated, async (_, res) => {
return res.send(articles);
});
router.get('/admin/records', checkIfAdmin, async (_, res) => {
return res.send(records);
});
export default router;
任何未分配管理员角色的令牌,如果尝试访问我们的管理资源,将会收到“未授权”错误。
还有很多内容可以探讨,但本文就只介绍这些。希望这些内容足以帮助您在服务器端开始使用 Firebase 身份验证。您可以通过查阅Firebase 文档了解更多功能。
文章来源:https://dev.to/emeka/secure-your-express-node-js-api-with-firebase-auth-4b5f