InfiniSynapse Partner SSO Integration Guide

本文面向希望在自己的产品中集成「使用 InfiniSynapse 登录」的第三方开发者。按照本文步骤接入后,你的应用可以:

  • 让用户以 InfiniSynapse 账号一键登录你的应用;
  • 登录完成后拿到用户的基本信息(ID、昵称、邮箱、头像等),与你自己的账号体系绑定;
  • 用户在完成登录的同时,也自动登录了 InfiniSynapse App(app.infinisynapse.cn),后续从你的应用跳转到 InfiniSynapse 时无需再次登录。

整个接入只需要你有一个能发 HTTP 请求的服务端,没有 SDK 依赖,任何语言都可以接。

1. 它是怎么工作的

先用一段大白话把流程讲清楚。整个登录过程一共 5 步,其中你的服务端只需要发 2 个 HTTP 请求:

你的应用                    InfiniSynapse                     用户浏览器
   │                            │                                │
   │ ① 创建登录会话 ────────────▶│                                │
   │ ◀──────── 返回 entryUrl ───│                                │
   │                            │                                │
   │ ② 把用户重定向到 entryUrl ─────────────────────────────────▶│
   │                            │◀── ③ 用户在 app 域完成登录 ────│
   │                            │                                │
   │ ◀───────── ④ 浏览器带一次性 code 跳回你的 returnUrl ────────│
   │                            │                                │
   │ ⑤ 用 code 换取用户信息 ───▶│                                │
   │ ◀──────── 返回用户资料 ────│                                │
  1. 创建会话:用户点击「使用 InfiniSynapse 登录」按钮时,你的服务端调用 InfiniSynapse 接口创建一个登录会话,拿到一个 entryUrl(指向 app.infinisynapse.cn 的入口页)。
  2. 重定向:把用户浏览器重定向到这个 entryUrl
  3. 用户登录:用户在 InfiniSynapse 页面完成登录(扫码、邮箱、手机号等方式均可)。如果用户此前已经登录过 InfiniSynapse,这一步会自动完成,用户几乎无感。
  4. 跳回你的应用:登录成功后,浏览器自动跳回你预先指定的 returnUrl,并附带一个一次性授权码 code
  5. 换取用户信息:你的服务端用这个 code 调用 InfiniSynapse 接口,换取用户资料,然后写入你自己的登录态,完成整个登录。

这个模式与微信、GitHub 等平台的 OAuth 授权码登录基本一致。关键的安全点在于:code 只在服务端之间传递兑换、只能用一次、几分钟内就过期,用户信息不会暴露在浏览器地址栏里。

2. 准备工作:申请接入凭证

接入前你需要一对凭证:clientId(客户端标识)和 clientSecret(客户端密钥),用于证明「这个请求确实来自你的应用」。

在页面上自助申请(推荐)

  1. 用你的 InfiniSynapse 账号登录 https://app.infinisynapse.cn/tasks
  2. 点击左下角「设置」齿轮图标,在菜单中选择 第三方接入
  3. 点击 创建接入应用,填写:
    • 应用名称:你的应用名,会用于后台展示;
    • 回调域名白名单:登录完成后允许跳回的域名,多个用逗号分隔。例如 example.com(会同时放行其子域名如 app.example.com)。本地开发调试可以填 localhost
    • Webhook URL(可选):如果你希望登录完成时 InfiniSynapse 主动通知你的服务端,填一个 HTTPS 接口地址,详见第 5 节。
  4. 创建成功后会弹窗展示 clientId / clientSecret(配置了 Webhook 还会有 webhookSecret)。这些密钥只展示这一次,请立即妥善保存。

在同一页面还可以查看已创建的应用列表、启用/禁用应用、重置密钥(旧密钥立即失效)。每个账号默认最多创建 5 个接入应用。

clientSecret 相当于你应用的密码,只能保存在你的服务端(环境变量、密钥管理系统等),绝对不要写进前端代码、App 客户端或公开仓库。如果怀疑泄露,请立即在页面上重置密钥。

通过 API 申请

也可以用你的登录 Token 直接调接口创建(Token 可从浏览器登录后的请求头中获取):

curl -X POST https://api.infinisynapse.cn/api/auth/partner/clients \
  -H "Authorization: Bearer <你的登录Token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "我的应用",
    "allowedReturnDomains": ["example.com"],
    "webhookUrl": "https://example.com/webhook/infini"
  }'

3. 接口基础信息

API 基础地址(国内)https://api.infinisynapse.cn/api
API 基础地址(海外)https://api.infinisynapse.com/api
服务端接口鉴权方式请求头 X-Client-Id + X-Client-Secret
内容类型application/json

所有接口返回统一信封结构:

{
  "code": 200,
  "message": "success",
  "data": { }
}

code === 200 表示成功,业务数据在 data 中;失败时 message 为错误说明。下文示例统一以 .cn 域名书写,海外环境替换为 .com 即可。

4. 接入步骤详解

第 1 步:服务端创建登录会话

用户点击你页面上的「使用 InfiniSynapse 登录」按钮后,你的服务端(不要在前端调,会暴露 secret)发起:

POST /api/auth/partner/sessions
X-Client-Id: partner_xxxxxxxx
X-Client-Secret: psk_xxxxxxxx
Content-Type: application/json

请求体:

字段必填说明
returnUrl登录成功后浏览器跳回的完整地址,域名必须在你申请时填写的白名单内,例如 https://example.com/auth/infini/callback
cancelUrl用户取消登录时跳回的地址,同样受白名单约束
state随机字符串,跳回时会原样带回,用于防 CSRF(强烈建议传,见第 6 节)
externalUserId你系统里的用户 ID。如果用户在你的应用里已有账号、只是做账号绑定,可以传,回调和 Webhook 里会原样带回
metadata任意 JSON 对象,回调时原样带回,可存放你的业务上下文

响应示例:

{
  "code": 200,
  "message": "success",
  "data": {
    "sessionId": "ps_1549cddc5049bf286f9858ce5ae873b2",
    "entryUrl": "https://app.infinisynapse.cn/auth/entry?session=ps_1549cddc5049bf286f9858ce5ae873b2",
    "expiresIn": 600
  }
}

会话默认有效期 10 分钟(expiresIn,单位秒),过期后用户需要回到你的页面重新发起登录。

第 2 步:把用户重定向到 entryUrl

拿到 entryUrl 后,直接让浏览器跳转过去(HTTP 302 或前端 location.href 均可)。

用户会看到 InfiniSynapse 的登录页面;如果用户浏览器里已有 InfiniSynapse 登录态,则会跳过登录界面直接进入下一步,整个过程一闪而过。

第 3 步:接收回调,校验 state

登录完成后,浏览器会跳回你的 returnUrl,并附带两个查询参数:

https://example.com/auth/infini/callback?code=ac_xxxxxxxx&state=你传入的state
  • code:一次性授权码,有效期 5 分钟,只能兑换一次;
  • state:你在第 1 步传入的值,原样返回。

收到回调后请先校验 state 与你发起登录时保存的值一致,不一致则拒绝(可能是伪造的回调请求)。

第 4 步:服务端用 code 换取用户信息

POST /api/auth/partner/token
X-Client-Id: partner_xxxxxxxx
X-Client-Secret: psk_xxxxxxxx
Content-Type: application/json

{
  "code": "ac_xxxxxxxx",
  "grant_type": "authorization_code"
}

响应示例:

{
  "code": 200,
  "message": "success",
  "data": {
    "user": {
      "id": "68d21916d6802ec254b46975",
      "email": "user@example.com",
      "username": "user@example.com",
      "nickname": "张三",
      "avatar": "https://files.authing.co/avatar/xxx.png",
      "phone": "138****0000"
    },
    "externalUserId": "your-user-123",
    "sessionId": "ps_1549cddc...",
    "metadata": { "source": "your-app" }
  }
}

data.user.id 是用户在 InfiniSynapse 的唯一 ID,建议用它作为绑定键存入你的用户表(邮箱、昵称等都可能变化,ID 不会变)。拿到用户信息后,为用户创建/关联你自己的账号,写入你的登录态(Session、JWT 等),登录流程到此完成。

注意:code 兑换失败的常见原因是超过 5 分钟有效期、或被重复兑换。这时让用户回到你的登录页重新发起一次即可。

顺带完成的事:用户同时登录了 InfiniSynapse App

由于第 3 步的登录发生在 app.infinisynapse.cn 域名下,用户在完成你应用的登录时,InfiniSynapse App 的登录态也已经建立。之后你的应用里如果有「打开 InfiniSynapse」之类的入口,用户点过去就是已登录状态,无需再登录一次。

5. 可选能力

Webhook:登录完成主动通知

如果你在创建应用时配置了 webhookUrl,每当有用户通过你的应用完成登录,InfiniSynapse 会向该地址 POST 一条消息:

{
  "event": "partner.session.completed",
  "sessionId": "ps_xxx",
  "externalUserId": "your-user-123",
  "user": { "id": "...", "email": "...", "nickname": "..." },
  "metadata": { },
  "completedAt": 1751443200000
}

请求头携带 HMAC-SHA256 签名,用创建应用时返回的 webhookSecret 验签:

X-Infini-Signature: <hex(hmac_sha256(webhookSecret, 原始请求体))>

Node.js 验签示例:

import { createHmac, timingSafeEqual } from 'node:crypto'

function verifySignature(rawBody, signature, webhookSecret) {
  const expected = createHmac('sha256', webhookSecret).update(rawBody).digest('hex')
  return signature.length === expected.length
    && timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
}

你的接口返回 HTTP 2xx 即视为投递成功;失败会指数退避重试若干次。Webhook 是异步通知,不要把它作为登录完成的唯一依据,主流程仍以 code 兑换为准。

轮询会话状态

如果你的场景不方便接收浏览器回调(比如桌面客户端弹出系统浏览器登录),可以在创建会话后轮询状态:

GET /api/auth/partner/sessions/{sessionId}
X-Client-Id: partner_xxxxxxxx
X-Client-Secret: psk_xxxxxxxx

响应中 statuspending(等待登录)/ completed(已完成,此时 user 字段携带用户资料)/ expired(已过期)。建议轮询间隔 2 秒以上。

6. 安全须知

  1. clientSecret 只放服务端。所有携带 X-Client-Secret 的请求都必须从你的服务端发出。
  2. 务必使用并校验 state。发起登录时生成随机字符串存入用户会话,回调时比对,防止跨站请求伪造(CSRF)。
  3. code 是一次性的,兑换后立即失效;不要把它存下来复用。
  4. returnUrl 白名单。InfiniSynapse 只允许跳回你申请时登记的域名,如需新增域名,请重新创建应用或联系我们调整。
  5. 生产环境请全程使用 HTTPS,Webhook 地址也必须是 HTTPS。
  6. 密钥疑似泄露时,第一时间在 设置 → 第三方接入 页面重置密钥或禁用应用。

7. 接口速查表

接口鉴权用途
POST /api/auth/partner/sessionsX-Client-Id + X-Client-Secret创建登录会话,返回 entryUrl
GET /api/auth/partner/sessions/{sessionId}同上轮询会话状态(含用户信息)
POST /api/auth/partner/token同上用一次性 code 换取用户资料
POST /api/auth/partner/clients用户 Bearer Token自助创建接入应用
GET /api/auth/partner/clients用户 Bearer Token查看自己名下的接入应用
POST /api/auth/partner/clients/{clientId}/enabled用户 Bearer Token启用/禁用应用,body {"enabled": false}
POST /api/auth/partner/clients/{clientId}/rotate-secret用户 Bearer Token重置 clientSecret(旧的立即失效)

8. 完整示例(Node.js + Express)

下面是一个可直接运行的最小示例,包含发起登录、接收回调、兑换用户信息三个环节:

import { randomBytes } from 'node:crypto'
import express from 'express'

const PROXY_API = 'https://api.infinisynapse.cn/api'
const CLIENT_ID = process.env.INFINI_CLIENT_ID
const CLIENT_SECRET = process.env.INFINI_CLIENT_SECRET
const SELF_ORIGIN = 'https://example.com'   // 你的应用地址

const app = express()
const partnerHeaders = {
  'Content-Type': 'application/json',
  'X-Client-Id': CLIENT_ID,
  'X-Client-Secret': CLIENT_SECRET,
}

// 1. 用户点击登录:创建会话并重定向
app.get('/login', async (req, res) => {
  const state = randomBytes(16).toString('hex')
  req.session.oauthState = state   // 存入你的会话方案

  const resp = await fetch(`${PROXY_API}/auth/partner/sessions`, {
    method: 'POST',
    headers: partnerHeaders,
    body: JSON.stringify({
      returnUrl: `${SELF_ORIGIN}/auth/infini/callback`,
      state,
    }),
  })
  const json = await resp.json()
  if (json.code !== 200) return res.status(500).send(json.message)
  res.redirect(json.data.entryUrl)
})

// 2. 登录完成跳回:校验 state,用 code 换用户信息
app.get('/auth/infini/callback', async (req, res) => {
  const { code, state } = req.query
  if (!code || state !== req.session.oauthState) {
    return res.status(400).send('state 校验失败')
  }
  req.session.oauthState = undefined

  const resp = await fetch(`${PROXY_API}/auth/partner/token`, {
    method: 'POST',
    headers: partnerHeaders,
    body: JSON.stringify({ code, grant_type: 'authorization_code' }),
  })
  const json = await resp.json()
  if (json.code !== 200) return res.status(500).send(json.message)

  const infiniUser = json.data.user
  // 按 infiniUser.id 查找/创建你的本地用户,写入登录态
  req.session.userId = infiniUser.id
  res.redirect('/dashboard')
})

9. 常见问题

问:用户已经登录过 InfiniSynapse,还会看到登录页吗? 不会。入口页检测到已有登录态时会直接完成会话并跳回你的应用,整个过程通常在 1 秒内。

问:登录会话或 code 过期了怎么办? 会话默认 10 分钟、code 默认 5 分钟有效。过期后让用户回到你的登录入口重新发起即可,无其他副作用。

问:能拿到用户的哪些信息? 用户 ID(稳定唯一)、昵称、用户名、邮箱、头像、手机号。具体字段是否有值取决于用户注册时使用的登录方式,请做好空值兜底,绑定账号请始终以 user.id 为准。

问:一个账号能创建几个接入应用? 默认每个 InfiniSynapse 账号最多创建 5 个。如有更多需求请联系我们。

问:本地开发怎么调试?localhost 加入回调域名白名单即可,returnUrl 支持 http://localhost:端口 形式。