服务端接入
为你的网站接入手机绑定与确认流程,提供 API 参数说明和官方 SDK 示例。
AuthLink 生产 API 为 https://api.getauthlink.com;网站业务后台地址仍填写你自己的 API,官网域名不用于接口调用。
接入流程与信任边界
- 运营方为网站创建独立租户,把凭据交付网站后台。
- 后台从已登录 session 取得稳定用户 ID,生成随机 state、创建幂等键和独立消费幂等键。
- 创建 bind / confirm 请求,保存用户、会话、用途、state、requestId、expiresAt 及幂等键,再把公开 ticket 返回浏览器。
- 用户在 AuthLink App 中核对网站、账号和数字,然后批准请求。
- 后台查询到 approved / consumed 后,使用保存的 state 和消费幂等键调用 consume。
- 校验回执,在自己的数据库事务中以 requestId / verificationId 唯一约束记录确认,才向网页返回 confirmed。受保护业务检查该记录。
没有 webhook。approved、六位数字以及前端回调都不能直接放行业务。AuthLink 不接收 action、金额、transactionHash 或业务授权范围。
租户与鉴权
向运营方获取本站的 tenantId、apiKey、origin 和 expiresAt。新密钥永久有效;运营方仍可轮换、撤销密钥或停用租户。旧密钥保留原有效期,只在网站后台安全保存。
| 请求头 | 要求 |
|---|---|
Authorization
|
Bearer <tenant API key>,全部 integration 接口必填 |
Content-Type
|
POST 使用 application/json;拒绝未知字段和重复 JSON 键 |
Idempotency-Key
|
创建和消费必填,16–128 位字母、数字、下划线或连字符。建议 32 字节安全随机 Base64URL;同次网络重试复用 |
Origin / Cookie
|
API 不接受浏览器 Origin 或 Cookie;网页通过自己的网站后台接入 |
创建请求与参数
POST /v1/integration/requests · API origin:
{
"externalUserId": "stable-user-id",
"accountLabel": "Account display name",
"purpose": "bind",
"state": "REPLACE_WITH_SAVED_43_CHARACTER_RANDOM_STATE"
}
| 字段 | 类型 / 必填 | 说明 |
|---|---|---|
externalUserId
|
string / 是 | 1–128 字节可打印 ASCII(0x21–0x7e)。由登录 session 取得,不可回收或由浏览器决定 |
accountLabel
|
string / 是 | 非空,最多 160 个 UTF-8 字节;拒绝控制字符及指定双向控制字符 |
purpose
|
string / 是 | 首次绑定 bind,已绑定后确认 confirm
|
state
|
string / 是 | 32 字节安全随机数,无 padding Base64URL,43 字符;后台保存并在消费时原样核对 |
成功返回 HTTP 200 和 ticket:
| 返回字段 | 含义 |
|---|---|
requestId
|
43 字符请求标识 |
purpose / expiresAt
|
请求用途和 UTC ISO 时间;有效期 120 秒 |
qr
|
QR JSON 字符串:v、origin、requestId、token;只交给前端组件或 App,禁止写入日志 |
qrSvg
|
服务端兼容字段;官方网页包会自行生成 QR,不使用 innerHTML 插入此字段 |
pairingCode
|
当前六位字符串,保留前导零 |
displayCode
|
value、serverTime、validUntil、periodSeconds:30 |
同一账号的新请求会拒绝旧的未完成请求。数字更新不延长期限,不需要另建请求。
状态、消费与账号接口
| 方法 / 路径 | 输入 | 返回 / 说明 |
|---|---|---|
GET /v1/integration/requests/{id}
|
租户 bearer | requestId、status、expiresAt、displayCode(可选)。new / pending / approved / consumed / rejected / expired |
POST /v1/integration/requests/{id}/consume
|
租户 bearer、Idempotency-Key,body {"state":"saved-43-character-state"}
|
approved 后领取 confirmed 回执;网络结果不明时复用原消费键 |
POST /v1/integration/accounts/status
|
租户 bearer,body {"externalUserId":"..."}
|
externalUserId、status:unbound/bound/deleted、platform(未绑定为 null) |
POST /v1/integration/accounts/delete
|
租户 bearer,body {"externalUserId":"..."}
|
{"status":"deleted"};永久撤销本站 AuthLink 绑定,不能重绑;不删除 CMS 账号 |
| consume 返回字段 | 核验要求 |
|---|---|
status / authenticationPolicy
|
status 必须为 confirmed;authenticationPolicy 由官方 SDK 校验 |
requestId / tenantId / externalUserId
|
必须对应当前请求、租户、已登录用户和已保存会话 |
purpose / state / expiresAt
|
与后台持久保存值完全一致,期限未过 |
verificationId / platform / consumedAt
|
校验结果 ID、android / ios 与有效时间;以唯一约束落库 |
Go SDK 与方法
独立模块 authlink.local/sdk,Go 1.22+,只依赖标准库。适合 Go 网站后台;不需要引入整个 AuthLink 服务端或 MySQL 驱动。
安装与初始化
解压后保留 SDK 自己的 go.mod;仅将 SDK 模块接入你的网站项目。
SDK 当前随源码交付,未发布公共 Go module。在你的网站 Go 项目中解压,使用本地 replace:
mkdir -p third_party
tar -xzf authlink-go.tar.gz -C third_party
go mod edit -replace=authlink.local/sdk=./third_party/authlink-go
go mod edit -require=authlink.local/[email protected]
go mod tidy
import authlink "authlink.local/sdk"
client, err := authlink.New(authlink.Config{
Origin: "https://api.getauthlink.com", // API origin, not the marketing site
TenantID: os.Getenv("AUTHLINK_TENANT_ID"),
APIKey: os.Getenv("AUTHLINK_API_KEY"),
})
if err != nil { return err }
// Get userID and label from your authenticated website session.
state, err := authlink.RandomToken()
if err != nil { return err }
createKey, err := authlink.RandomToken()
if err != nil { return err }
consumeKey, err := authlink.RandomToken()
if err != nil { return err }
// Persist state, both keys and user/session first. Reuse them on retries.
ticket, err := client.CreateRequest(ctx, authlink.CreateInput{
ExternalUserID: userID, AccountLabel: label,
Purpose: "bind", State: state,
}, createKey)
if err != nil { return err }
expected := authlink.Expected{
RequestID: ticket.RequestID, ExternalUserID: userID,
Purpose: ticket.Purpose, State: state, ExpiresAt: ticket.ExpiresAt,
}
// Save expected and consumeKey before returning the public ticket.
// Load saved expected / consumeKey after checking session, CSRF and ownership.
status, err := client.Status(ctx, expected.RequestID)
if err != nil { return err }
if status.Status == "approved" || status.Status == "consumed" {
result, err := client.Consume(ctx, expected, consumeKey)
if err != nil { return err }
// The SDK has validated the full receipt. Persist a unique confirmation
// in your own transaction, associated with this user/session and business.
_ = result
}
| Config 参数 | 说明 |
|---|---|
Origin / TenantID / APIKey | 必填,API HTTPS origin(无末尾斜杠)、租户 ID、私有后台密钥 |
AllowLocalHTTP | 默认 false;只允许回环 / localhost 开发测试 |
HTTPClient | 可选 *http.Client;SDK 复制后禁止重定向、禁用 CookieJar。transport 需可信,每次请求有 10 秒上限,父 context 和更短的 client timeout 优先 |
| 方法 | 返回与用途 |
|---|---|
CreateRequest(ctx, CreateInput, key) | (Ticket, error),CreateInput:ExternalUserID、AccountLabel、Purpose、State;默认用途 confirm,首次绑定 bind |
Status(ctx, requestID) | (RequestStatus, error),查询状态与当前 displayCode,不能直接当作业务确认 |
Consume(ctx, Expected, key) | (Confirmation, error),消费并校验策略、租户、用户、请求、用途、state、期限、结果 ID 和平台 |
WaitForConfirmation(ctx, Expected, key, interval) | (Confirmation, error),可选后台轮询;approved / consumed 后消费。interval=0 默认 1500 毫秒,允许 100 毫秒–10 秒 |
AccountStatus(ctx, externalUserID) | (AccountStatus, error),查询 unbound / bound / deleted;Platform 为可空 *string |
DeleteAccount(ctx, externalUserID) | error;永久撤销本站 AuthLink 绑定,原账号不可重绑,不删除 CMS 用户 |
RandomToken() | (string, error),32 字节安全随机 Base64URL;state、创建键和消费键分别生成并保存 |
ValidateConfirmation(result, expected, tenantID, now) | error,纯字段校验;不是签名验证器,不能把浏览器自报回执当可信授权 |
Expected 包含 RequestID、ExternalUserID、Purpose、State、ExpiresAt;从后台已保存的请求读取,保持原 expiresAt 字符串。返回类型均带协议 JSON tag,可编码给官方网页组件。SDK 拒绝未知字段、重复键和大小写别名;协议增加字段时需要同步升级 SDK。
等待、取消与错误处理
result, err := client.WaitForConfirmation(ctx, expected, consumeKey, 0)
switch {
case err == nil:
// Persist result in your own transaction before updating UI or authorizing work.
_ = result
case errors.Is(err, authlink.ErrRejected):
// The user rejected the request.
case errors.Is(err, authlink.ErrExpired):
// The request expired.
case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
// This call was canceled or timed out; the platform request is not revoked.
default:
var api *authlink.APIError
if errors.As(err, &api) {
// Handle api.StatusCode / api.Code; retry with the same consumption key.
}
}
Go 完整示例
示例使用两个阶段:create 创建请求,result 查询并消费已保存的请求。只在用户完成手机确认后执行 result。示例不包含网站的会话管理和数据库持久化,不能直接替代业务授权。
展开或收起完整 Go 示例
// This example creates a request, then queries/consumes a previously saved one.
// Supply tenant credentials only through your backend's private environment.
package main
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"time"
authlink "authlink.local/sdk"
)
func run() error {
mode := flag.String("mode", "create", "create or result")
user := flag.String("user", "", "stable user ID from your authenticated session")
label := flag.String("label", "", "account display label")
purpose := flag.String("purpose", "bind", "bind or confirm")
flag.Parse()
client, err := authlink.New(authlink.Config{Origin: os.Getenv("AUTHLINK_ORIGIN"), TenantID: os.Getenv("AUTHLINK_TENANT_ID"), APIKey: os.Getenv("AUTHLINK_API_KEY")})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
switch *mode {
case "create":
// For this standalone example, supply already-generated and saved values
// through the environment. A website must persist them before networking.
ticket, err := client.CreateRequest(ctx, authlink.CreateInput{ExternalUserID: *user, AccountLabel: *label, Purpose: *purpose, State: os.Getenv("AUTHLINK_STATE")}, os.Getenv("AUTHLINK_CREATE_KEY"))
if err != nil {
return err
}
// Contains QR token: deliver to the intended user's UI; never log it.
return json.NewEncoder(os.Stdout).Encode(ticket)
case "result":
expected := authlink.Expected{RequestID: os.Getenv("AUTHLINK_REQUEST_ID"), ExternalUserID: *user, Purpose: *purpose, State: os.Getenv("AUTHLINK_STATE"), ExpiresAt: os.Getenv("AUTHLINK_EXPIRES_AT")}
status, err := client.Status(ctx, expected.RequestID)
if err != nil {
return err
}
if status.Status != "approved" && status.Status != "consumed" {
return json.NewEncoder(os.Stdout).Encode(status)
}
result, err := client.Consume(ctx, expected, os.Getenv("AUTHLINK_CONSUME_KEY"))
if err != nil {
return err
}
// In a website, persist result atomically with unique request/result IDs,
// user/session and business association before returning confirmed.
_ = result
fmt.Fprintln(os.Stderr, "Receipt validated; this example does not persist or authorize business.")
return nil
default:
return errors.New("mode must be create or result")
}
}
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
运行参数
| 参数 | 用途 |
|---|---|
-mode | create 创建请求;result 查询和消费结果,默认 create。 |
-user | 已登录网站用户的稳定 ID,由后台会话取得。 |
-label | 创建请求时展示的账号名称。 |
-purpose | 首次绑定使用 bind;后续确认使用 confirm,示例默认 bind。 |
后台环境变量
| 变量 | 用途 |
|---|---|
AUTHLINK_ORIGIN / AUTHLINK_TENANT_ID / AUTHLINK_API_KEY | 由运营方提供的 API 地址、租户 ID 和后台密钥,仅保存在网站后台。 |
AUTHLINK_STATE / AUTHLINK_CREATE_KEY / AUTHLINK_CONSUME_KEY | 后台分别生成并持久保存的 state、创建幂等键和消费幂等键;重试时复用。 |
AUTHLINK_REQUEST_ID / AUTHLINK_EXPIRES_AT | result 阶段从后台保存的 ticket 读取请求 ID 和原始到期时间。 |
完整示例已包含在 SDK 整包的 examples/quickstart/main.go 中。可复制上方代码保存为 main.go;凭据通过后台环境注入,不要写入代码或浏览器。
# Run from a Go project that already includes the SDK module.
# Supply the private environment variables listed above before running.
export AUTHLINK_ORIGIN=https://api.getauthlink.com
go run . -mode create -user stable-user-id -label "Example account" -purpose bind
# After phone approval, load the SAME saved request context before running:
go run . -mode result -user stable-user-id -purpose bind
Node SDK 与方法
// Copy /sdk/node/ and /sdk/browser/ with their relative paths. Server import only.
import { AuthLink, randomToken } from './sdk/node/authlink.mjs';
const auth = new AuthLink({
origin: 'https://api.getauthlink.com',
tenantId: process.env.AUTHLINK_TENANT_ID,
apiKey: process.env.AUTHLINK_API_KEY,
});
// user must come from your authenticated session. This is the creation stage.
const state = randomToken();
const createKey = randomToken();
const consumeKey = randomToken();
// Persist these values and user/session before networking. Reuse on retries.
const ticket = await auth.createRequest({
externalUserId: user.id, accountLabel: user.displayName,
purpose: 'bind', state,
}, createKey);
const expected = { requestId: ticket.requestId, expiresAt: ticket.expiresAt,
externalUserId: user.id, purpose: 'bind', state };
// Save expected, consumeKey and session association before returning ticket.
// Your result endpoint loads the saved request context:
const status = await auth.status(expected.requestId);
if (['approved', 'consumed'].includes(status.status)) {
const verified = await auth.consume(expected, consumeKey);
// The SDK has verified the receipt. Persist a unique confirmation atomically.
// Return verified to the browser only after the database commit succeeds.
}
| 构造参数 / 方法 | 说明 |
|---|---|
origin / tenantId / apiKey
|
必填;API HTTPS origin(无末尾斜杠)、租户 ID 和私有密钥 |
allowLocalHttp
|
默认 false;仅本机回环开发 |
createRequest(input, key)
|
创建;input 字段见上表,purpose 未传时默认 confirm |
status(requestId, signal?)
|
查询状态;signal 为可选 AbortSignal |
consume(expected, key, signal?)
|
消费并校验回执,返回已验证的结果 |
waitForConfirmation(expected, options)
|
后台轮询工具;options.idempotencyKey 与 onConfirmed 必填;可选 onRejected / onExpired、signal、intervalMs(默认 1500,范围 100–10000 毫秒);仍由业务持久化结果 |
accountStatus(id) / deleteAccount(id)
|
查询 / 永久撤销本站账号绑定 |
安装说明、方法参数和使用示例均在本页。Node SDK 与所需依赖已合并为一个压缩包,解压后保留 node 与 browser 的相对目录;网页组件通过 npm 安装。
mkdir -p sdk
tar -xzf authlink-node.tar.gz -C sdk
# Import ./sdk/node/authlink.mjs from your backend project.
错误处理
| HTTP | 常见 code | 处理 |
|---|---|---|
| 400 | INVALID_REQUEST / INVALID_SUBJECT / INVALID_DISPLAY_TEXT / INVALID_IDEMPOTENCY_KEY | 按字段约束修正输入;不要自动生成新请求掩盖未知结果 |
| 401 | UNAUTHORIZED / STATE_MISMATCH / SERVER_CREDENTIAL_REQUIRED | 检查私有凭据、state、Host 和是否绕过网站后台 |
| 404 | NOT_FOUND | 请求或发布渠道不存在 |
| 409 | APPROVAL_REQUIRED / REQUEST_EXPIRED / REQUEST_USED / IDEMPOTENCY_CONFLICT / ACCOUNT_ALREADY_BOUND / ACCOUNT_NOT_BOUND | 按业务状态处理,过期后由用户重新发起,保留幂等恢复语义 |
| 429 | RATE_LIMITED / BUSY | 退避重试,复用原幂等键 |
| 503 | ATTESTATION_UNAVAILABLE | 验证服务暂不可用,稍后重试 |
错误 body 为 {"code":"..."}。未知错误不当成成功。接口响应不能共享缓存,禁止记录 bearer、二维码 token、完整证明和原始秘密。