Server integration

服务端接入

为你的网站接入手机绑定与确认流程,提供 API 参数说明和官方 SDK 示例。

AuthLink 生产 API 为 https://api.getauthlink.com;网站业务后台地址仍填写你自己的 API,官网域名不用于接口调用。

接入流程与信任边界

通过官方 SDK 接入手机确认流程。接入凭据仅保存在网站后台,请按下方说明创建请求并处理确认结果。
  1. 运营方为网站创建独立租户,把凭据交付网站后台。
  2. 后台从已登录 session 取得稳定用户 ID,生成随机 state、创建幂等键和独立消费幂等键。
  3. 创建 bind / confirm 请求,保存用户、会话、用途、state、requestId、expiresAt 及幂等键,再把公开 ticket 返回浏览器。
  4. 用户在 AuthLink App 中核对网站、账号和数字,然后批准请求。
  5. 后台查询到 approved / consumed 后,使用保存的 state 和消费幂等键调用 consume。
  6. 校验回执,在自己的数据库事务中以 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 与有效时间;以唯一约束落库
删除表示永久撤销 AuthLink 绑定。不要据此自动删除 CMS 用户;需要确认的业务也不能在撤销后跳过验证。

Go SDK 与方法

独立模块 authlink.local/sdk,Go 1.22+,只依赖标准库。适合 Go 网站后台;不需要引入整个 AuthLink 服务端或 MySQL 驱动。

本页已包含安装步骤、参数说明、错误处理和完整示例,可直接阅读并复制代码。

下载 Go SDK 源码包 · 在线查看完整示例 →

安装与初始化

解压后保留 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.
    }
}
没有自动网络重试。网络结果不明时复用已保存的幂等键;仅已消费、校验且在网站数据库落库的结果可以用于业务。网页轮询接口通常使用 Status + Consume,WaitForConfirmation 适合后台长等待。

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)
	}
}

运行参数

参数用途
-modecreate 创建请求;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_ATresult 阶段从后台保存的 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 安装。

下载 Node SDK 整包

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、完整证明和原始秘密。