Auth0 连接器
通过 Tajo 将 Auth0 连接到 Brevo,将已认证的用户档案同步为营销联系人,根据认证事件触发自动化,并以身份和访问管理洞察丰富您的客户数据。
概览
| 属性 | 值 |
|---|---|
| 平台 | Auth0(by Okta) |
| 类别 | 身份与访问(自定义) |
| 设置复杂度 | 中等 |
| 官方集成 | 否 |
| 同步数据 | 用户、事件、角色、身份 |
| 认证方式 | 机器对机器 OAuth 2.0 |
功能
- 用户档案同步 - 将 Auth0 用户档案同步到 Brevo 联系人
- 认证事件 - 在登录、注册和密码重置时触发自动化
- 基于角色的细分 - 根据 Auth0 角色和权限细分联系人
- 社交身份数据 - 用社交登录档案信息丰富联系人
- 登录活动跟踪 - 跟踪最后登录、登录次数和设备数据
- 多租户支持 - 跨多个 Auth0 租户同步用户
前提条件
开始之前,请确保您已具备:
- 具有 API 访问权限的 Auth0 账户
- 在 Auth0 中注册的机器对机器应用
- 授予 M2M 应用的管理 API 权限
- 具有 API 访问权限的 Brevo 账户
- 具有连接器权限的 Tajo 账户
认证
机器对机器 OAuth 2.0
# Create an M2M application in Auth0 Dashboardexport AUTH0_DOMAIN=your-tenant.auth0.comexport AUTH0_CLIENT_ID=your_client_idexport AUTH0_CLIENT_SECRET=your_client_secretexport AUTH0_AUDIENCE=https://your-tenant.auth0.com/api/v2/// Get Management API access tokenconst tokenResponse = await fetch( `https://${process.env.AUTH0_DOMAIN}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: process.env.AUTH0_CLIENT_ID, client_secret: process.env.AUTH0_CLIENT_SECRET, audience: process.env.AUTH0_AUDIENCE, grant_type: 'client_credentials' }) });
const { access_token } = await tokenResponse.json();// Token is valid for 24 hours by defaultAPI 权限
仅向您的 M2M 应用授予所需范围:read:users、read:user_idp_tokens、read:roles 和 read:logs。非必要不授予写入权限。
配置
基础设置
connectors: auth0: enabled: true domain: "${AUTH0_DOMAIN}" client_id: "${AUTH0_CLIENT_ID}" client_secret: "${AUTH0_CLIENT_SECRET}" audience: "https://${AUTH0_DOMAIN}/api/v2/"
sync: users: true events: true roles: true schedule: "0 */4 * * *" # Every 4 hours
lists: all_users: 20 verified_users: 21 social_login: 22字段映射
field_mapping: email: email given_name: FIRSTNAME family_name: LASTNAME nickname: NICKNAME picture: AVATAR_URL email_verified: EMAIL_VERIFIED logins_count: LOGIN_COUNT last_login: LAST_LOGIN_DATE created_at: SIGNUP_DATE user_metadata.phone: SMS user_metadata.company: COMPANY app_metadata.plan: SUBSCRIPTION_PLAN app_metadata.role: USER_ROLEAPI 端点
| 端点 | 方法 | 描述 |
|---|---|---|
https://{domain}/api/v2/users | GET | 列出或搜索用户 |
https://{domain}/api/v2/users/{id} | GET | 获取用户 |
https://{domain}/api/v2/users/{id} | PATCH | 更新用户元数据 |
https://{domain}/api/v2/users/{id}/roles | GET | 获取用户角色 |
https://{domain}/api/v2/roles | GET | 列出所有角色 |
https://{domain}/api/v2/logs | GET | 获取日志事件 |
https://{domain}/api/v2/stats/active-users | GET | 获取活跃用户数 |
https://{domain}/api/v2/stats/daily | GET | 获取每日统计 |
https://{domain}/oauth/token | POST | 获取访问令牌 |
代码示例
初始化连接器
import { TajoClient } from '@tajo/sdk';
const tajo = new TajoClient({ apiKey: process.env.TAJO_API_KEY, brevoApiKey: process.env.BREVO_API_KEY});
await tajo.connectors.connect('auth0', { domain: process.env.AUTH0_DOMAIN, clientId: process.env.AUTH0_CLIENT_ID, clientSecret: process.env.AUTH0_CLIENT_SECRET});将用户同步到 Brevo
// Paginate through Auth0 userslet page = 0;const perPage = 50;let hasMore = true;
while (hasMore) { const response = await fetch( `https://${domain}/api/v2/users?` + new URLSearchParams({ page: page.toString(), per_page: perPage.toString(), include_totals: 'true', search_engine: 'v3', q: 'email_verified:true' }), { headers: { 'Authorization': `Bearer ${accessToken}` } } );
const { users, total } = await response.json();
for (const user of users) { await tajo.contacts.sync({ email: user.email, attributes: { FIRSTNAME: user.given_name, LASTNAME: user.family_name, LOGIN_COUNT: user.logins_count, LAST_LOGIN_DATE: user.last_login, SIGNUP_DATE: user.created_at, EMAIL_VERIFIED: user.email_verified }, listIds: [20] }); }
page++; hasMore = (page * perPage) < total;}通过日志流跟踪认证事件
// Set up Auth0 Log Stream webhook// Configure in Auth0 Dashboard > Monitoring > Streams
app.post('/webhooks/auth0', async (req, res) => { // Verify authorization header const authHeader = req.headers.authorization; if (authHeader !== `Bearer ${process.env.AUTH0_WEBHOOK_TOKEN}`) { return res.status(401).send('Unauthorized'); }
const logs = req.body;
for (const log of logs) { switch (log.data.type) { case 's': // Successful login await tajo.events.track({ email: log.data.details.email, event: 'user_login', properties: { ip: log.data.ip, user_agent: log.data.user_agent, connection: log.data.connection } }); break; case 'ss': // Successful signup await tajo.contacts.sync({ email: log.data.details.email, attributes: { SIGNUP_DATE: log.data.date }, listIds: [20] }); break; case 'sp': // Successful password change await tajo.events.track({ email: log.data.details.email, event: 'password_changed' }); break; } }
res.status(200).send('OK');});基于角色的细分
// Sync user roles for segmentationconst rolesResponse = await fetch( `https://${domain}/api/v2/users/${userId}/roles`, { headers: { 'Authorization': `Bearer ${accessToken}` } });
const roles = await rolesResponse.json();const roleNames = roles.map(r => r.name).join(', ');
await tajo.contacts.update(userEmail, { attributes: { USER_ROLE: roleNames, IS_ADMIN: roles.some(r => r.name === 'admin') }});速率限制
| 端点类别 | 限制 | 说明 |
|---|---|---|
| 管理 API | 50 请求/秒(免费) | 每租户 |
| 管理 API | 100 请求/秒(付费) | 每租户 |
| 认证 API | 因计划而异 | 基于计划 |
| 日志流 | 实时 | 投递无速率限制 |
| 分页 | 每页最多 50 条 | 使用 page 和 per_page 参数 |
需要分页
Auth0 管理 API 每页最多返回 50 条结果。始终使用 page 和 per_page 参数实现分页。包含 include_totals=true 以获取总数。
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 401 Unauthorized | 令牌已过期 | 请求新的 M2M 令牌(24 小时有效期) |
| 403 Forbidden | 缺少范围 | 向 M2M 应用授予所需权限 |
| 用户列表为空 | 搜索查询错误 | 使用 v3 引擎的 Lucene 查询语法 |
| 元数据缺失 | 未设置元数据 | 检查 user_metadata 和 app_metadata |
| 速率限制 429 | 请求过多 | 使用重试头实现退避 |
调试模式
connectors: auth0: debug: true log_level: verbose log_sync: true最佳实践
- 使用日志流 - 实时事件流而非轮询日志 API
- 实施分页 - 始终对大型租户的用户列表查询进行分页
- 缓存 M2M 令牌 - 在接近过期前复用令牌(默认 24 小时有效期)
- 使用搜索引擎 v3 - 使用 Lucene 查询语法高效搜索用户
- 仅同步已验证用户 - 过滤
email_verified:true以避免未验证联系人 - 利用用户元数据 - 在 Auth0 user_metadata 中存储自定义属性以便同步
安全
- 机器对机器 OAuth - 服务器到服务器认证的客户端凭据授权
- 范围权限 - 授予最低所需管理 API 范围
- 令牌轮换 - M2M 令牌默认 24 小时后过期
- 日志流认证 - 使用 Bearer 令牌验证 Webhook 端点
- 租户隔离 - 每个 Auth0 租户单独配置
- 加密传输 - 所有 API 通信使用 TLS 1.2+