Express + TypeScript + OpenFGA 权限控制实践指南
添加 API 授权
在设置 API 授权时,首先需要通过命令行工具生成设备确认码,并在浏览器中激活设备。如果不使用不透明令牌,则无需在 Auth0 中为 API 创建客户端应用程序,但必须在租户中注册 API。以下是使用 Auth0 CLI 注册 API 的命令:
auth0 apis create
--name "Document API"
--identifier https://document-api.okta.com
--offline-access=false
在提示时,将作用域留空并保留默认值。
接下来,克隆文档 API 的代码存储库,该存储库已经实现了基本的请求处理逻辑:
git clone https://github.com/indiepopart/express-typescript-fga.git
存储库包含两个文件夹:“start”和“final”。“start”文件夹中是一个基础的 Node.js 项目。使用您喜欢的 IDE 打开该项目,并安装 express-[oauth](https://www.explinks.com/wiki/oauth/)2-jwt-bearer 依赖:
cd express-typescript-fga/start
npm install express-oauth2-jwt-bearer
创建令牌验证中间件
在 src/middleware/auth0.middleware.ts 文件中创建一个用于验证令牌的中间件,代码如下:
// src/middleware/auth0.middleware.ts
import * as dotenv from "dotenv";
import { auth } from "express-oauth2-jwt-bearer";
dotenv.config();export const validateAccessToken = auth({
issuerBaseURL: https://${process.env.AUTH0_DOMAIN},
audience: process.env.AUTH0_AUDIENCE,
});
在路由中调用 validateAccessToken,例如:
// src/documents/document.router.ts
documentRouter.get("/", validateAccessToken, async (req, res, next) => {
try {
const documents = await getAllDocuments();
res.status(200).json(documents);
} catch (error) {
next(error);
}
});
添加错误处理
在 error.middleware.ts 文件中添加错误处理逻辑,代码如下:
import { Request, Response, NextFunction } from "express";
import { InvalidTokenError, UnauthorizedError } from "express-oauth2-jwt-bearer";
import mongoose from "mongoose";
export const errorHandler = (
error: any,
request: Request,
response: Response,
next: NextFunction
) => {
console.log(error); if (error instanceof InvalidTokenError) {
response.status(error.status).json({ message: "Bad credentials" });
return;
} if (error instanceof UnauthorizedError) {
response.status(error.status).json({ message: "Requires authentication" });
return;
} if (error instanceof mongoose.Error.ValidationError) {
response.status(400).json({ message: "Bad Request" });
return;
} if (error instanceof mongoose.Error.CastError) {
response.status(400).json({ message: "Bad Request" });
return;
} response.status(500).json({ message: "Internal Server Error" });
};
配置环境变量
将 .env.example 文件复制为 .env,并添加以下内容:
AUTH0_AUDIENCE=https://document-api.okta.com
AUTH0_DOMAIN=
启动 MongoDB 数据库
使用以下命令启动 MongoDB 数据库和管理界面:
docker compose up mongodb mongo-express
使用 Auth0 CLI 获取测试访问令牌:
auth0 test token -a https://document-api.okta.com -s openid
然后使用 cURL 创建文档:
curl -i -X POST
-H "Authorization:Bearer $ACCESS_TOKEN"
-H "Content-Type: application/json"
-d '{"name": "planning.doc"}'
http://localhost:6060/api/documents
成功的响应示例如下:
{
"name": "planning.doc",
"_id": "66feb9c1f106b84c28644d3e",
"__v": 0
}
在 OpenFGA 中初始化授权模型
授权模型通过定义用户类型、对象类型及其关系来实现。在本指南中,我们将使用一个简化的授权模型。首先,在 start/OpenFGA 目录下创建文件 auth-model.fga,内容如下:
模型模式1.1
类型 用户
类型 文档
关系定义 所有者:[用户,域#成员]或父级所有者
定义 编写者:[用户、域#成员].或父级的所有者或编写者
定义 评论者:[用户和域#成员][父级的编写者或评论者
定义 查看器:[user,user:*,域#会员]或父定义父级的评论者或查看器:[文档]
类型 域
关系定义 成员:[用户]
将模型转换为 JSON 格式:
fga model transform --file=auth-model.fga > auth-model.json
启动 OpenFGA 服务:
docker compose up openfga
创建一个存储:
export FGA_API_URL=http://localhost:8090
fga store create --name "documents-fga"
将输出的 store-id 设置为环境变量,并写入模型:
export FGA_STORE_ID=
fga model write --store-id=${FGA_STORE_ID} --file auth-model.json
使用 OpenFGA 添加细粒度授权
在 .env 文件中添加以下变量:
FGA_API_URL=http://localhost:8090
FGA_STORE_ID=
FGA_MODEL_ID=
安装 OpenFGA Node.js SDK:
npm install @openfga/sdk
创建权限检查中间件,代码如下:
// src/middleware/openfga.middleware.ts
import * as dotenv from "dotenv";
import { NextFunction, Request, Response } from "express";
import { ClientCheckRequest, OpenFgaClient } from "@openfga/sdk";
dotenv.config();export class PermissionDenied extends Error {
constructor(message: string) {
super(message);
}
}const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL,
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID,
});export const forView = (req: Request): ClientCheckRequest => {
const userId = req.auth?.payload.sub;
return { user: user:${userId}, object: document:${req.params.id}, relation: "viewer" };
};// 其他权限检查方法省略...export const checkPermissions = (createTuple: (req: Request) => ClientCheckRequest | null) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const tuple = createTuple(req);
if (!tuple) {
next();
return;
}
const result = await fgaClient.check(tuple);
if (!result.allowed) {
next(new PermissionDenied("Permission denied"));
return;
}
next();
} catch (error) {
next(error);
}
};
};
在路由中调用中间件:
documentRouter.get(
"/:id",
validateAccessToken,
checkPermissions(forView),
async (req, res, next) => {
try {
const document = await findDocumentById(req.params.id);
if (!document) {
res.status(404).json({ message: "Document not found" });
return;
}
res.status(200).json(document);
} catch (error) {
next(error);
}
}
);
向 Express API 发送请求
运行 API 并尝试读取操作:
curl -i -H "Authorization: Bearer $ACCESS_TOKEN" localhost:6060/api/documents
如果权限不足,将返回 403 Forbidden:
{ "message": "Permission denied" }
使用 FGA CLI 授予读取权限后,重试操作即可成功。
fga tuple write --store-id=${FGA_STORE_ID} --model-id=$FGA_MODEL_ID
'user:' viewer document:
原文链接: https://auth0.com/blog/express-typescript-fga/
最新文章
- python并行组合生成原理及实现
- 终极对决:KimiGPT与GLM-4文本生成模型API深度比较
- 如何获取OpenWeather API Key 密钥(分步指南)
- 从Talkie到DeepSeek:揭秘AI应用出海的盈利路径
- 确保OAuth 2.0访问令牌安全,使用持有者凭证证明
- 利用JAVA语言调用豆包大模型接口实战指南
- 如何调用 GraphQL Admin API 查询非Rest API 可以查询到的数据
- API – API是什么?
- 超越网关API(第二部分):如何扩展Envoy … – Tetrate
- 使用 Azure 应用程序网关和 Azure 保护外部 API
- 如何使用 PostgREST 和 Apache APISIX 构建高效、安全的 RESTful API 解决方案
- 什么是SQL注入?理解、风险与防范技巧