@atlasauth/backend

中文文档
0.55.0发布于 2026/11/16TypeScriptCommonJS近 30 天下载 7536

用于节点、德诺、邦和边缘运行时间的服务器侧图集。 它有三样东西:

中文使用文档
文档目录28 节
  1. 校验会话
  2. 用重定向( OpenID 连接) 签名
  3. @atlasauth/backend/rp : 请求 – 任何运行时间的响应处理器
  4. Next.js (App路由器) (英语)
  5. 快递
  6. 测试离线
  7. 管理客户端( 密钥)
  8. 检查您的设置
  9. 退缩和可观察性
  10. 设计登录页面
  11. API 密钥支出上限
  12. 镜像 Atlas 数据输入您的数据库
  13. 本地 API 密钥镜像
  14. 会话保存
  15. 严格的请求机构
  16. 错误
  17. 平台客户端( 平台密钥)
  18. 域名移动、 登出扇并管理电子邮件
  19. 后通道登录
  20. 网络呼号
  21. 网络加密上的标准 Webhooks
  22. 类型事件
  23. 将用户映射到数据库
  24. 需要外交部
  25. 中国证监会
  26. 移交归属
  27. 接收推荐协议( 移交平台)
  28. 双向同步

@ atlasauth/ 后端

用于节点、德诺、邦和边缘运行时间的服务器侧图集。 它有三样东西:

  • 核实 阿特拉斯会话令牌、webhooks和后通道登录令牌
  • 运行主机- 直接( OpenID Connect) 登录的服务器侧面
  • 为后端 API 提供一个输入客户端, 使用您的密钥( W)sk_...(中文(简体) )
  • 给您一个平台 API 的打字客户端, 使用您的平台密钥( Namepsk_...(中文(简体) )

什么都不需要,不过 fetch。 。 。 .

npm install @atlasauth/backend

校验会话

import { AtlasBackend } from '@atlasauth/backend';

const atlas = new AtlasBackend({ issuer: 'https://clerk.example.com' });
const result = await atlas.verify(sessionToken);
if (result.ok) console.log(result.claims.sub);

JWKS是从发行商找到并被缓存的. 共享主机上的例子也通过 instanceId (或 publishableKey) (中文(简体) ).

用重定向( OpenID 连接) 签名

oidc 运行主机-重定向集成的依赖方侧:状态、非ce和PKCE、流程饼干、代码交换和ID-token检查(仅RS256, iss, (中文(简体) ), aud =你的客户身份, nonce, (中文(简体) ), exp) (中文(简体) ).

import { oidc, AtlasOidcError } from '@atlasauth/backend';

const config = {
  issuer: 'https://bright-fox-42-fapi.atlasauth.net', // https:// + frontend_api_host
  clientId: process.env.ATLAS_CLIENT_ID!,
  redirectUri: 'https://app.example.com/auth/callback',
  secret: process.env.OIDC_COOKIE_SECRET!, // 32+ random characters; seals the flow cookie
};

// Start: set the flow cookie, redirect.
const { url, cookie } = await oidc.start({ ...config, scope: 'openid profile email' });

// Callback: one call does every check.
const identity = await oidc.callback({
  ...config,
  clientSecret: process.env.ATLAS_CLIENT_SECRET!,
  query: request.url, // or URLSearchParams, or req.query
  cookie: request.headers.get('cookie'),
});
// identity: { sub, namespacedSub, email, emailVerified, name, idToken, accessToken, refreshToken?, expiresAt, claims, clearCookie }

// Sign out: end your session, then end the Atlas one.
const logout = await oidc.logoutUrl({ ...config, postLogoutRedirectUri: 'https://app.example.com/', idTokenHint });
  • 流饼干是 httpOnly, (中文(简体) ), SameSite=Lax, (中文(简体) ), Secure (a项除外) http://localhost ),被瞄准回调路径,持续10分钟. 与 secret 它被加密(AES-GCM),所以不能被读取或被伪造. 无 secret 它只是编码。 总是通过一个生产.
  • namespacedSub 实值 ${issuer}#${sub}。如果您接受不止一个实例,则其上的关键用户,所以两个实例的用户从未碰撞过.
  • clientAuth 实值 client_secret_basic (以秘密示弃), client_secret_post,或 none (公共客户端,仅限PKCE).
  • new AtlasBackend({ issuer }).oidc 使用相同的方法 issuer 填报.
  • 你自己的密码交换了吗? oidc.verifyIdToken(idToken, { issuer, clientId, nonce }) 运行只是象征性的支票.

失败投出 AtlasOidcError 拥有稳定 code数字 :

code含义
state_mismatch没有流饼干,不一样 state或者是超过10分钟的水流 再来一次.
access_deniedAtlas 以 OAuth 重定向返回 error。准确值为 oauthError (单位:千美元)access_denied, (中文(简体) ), login_required(中文(简体) ).
issuer_mismatch召回的电话 iss 参数名称为不同的服务器.
invalid_request召回没有 code。 。 。 .
token_exchange_failed符号端点拒绝代码或客户端( S)oauthError, (中文(简体) ), status) (中文(简体) ).
invalid_tokenID 符号没有通过检查: 算法, 签名, iss, (中文(简体) ), aud编号 :azp, (中文(简体) ), nonce, (中文(简体) ), exp编号 :iat 或者说 at_hash。 。 。 .
discovery_failed发现文件或签名密钥无法被取出或与发行人不符.
reauthentication_requiredstart 请求 maxAge 和标识符的 auth_time 较旧(或缺失).
subject_mismatchstart 请求 expectedSub (一个步骤)和一个不同的用户报名.

每一个错误来自 callback 携带 clearCookie: 随错误页一起发送.

start 还要 uiLocales, (中文(简体) ), maxAge (回调后执行) auth_time, (中文(简体) ), returnTo (同一地点路径,返回为 identity.returnTo和 expectedSub。该身份带有 sid (Atlas会议d)和 authTime。 。 。 .

@atlasauth/backend/rp: 请求 – 任何运行时间的响应处理器

同样的流量 (Request) => Response 处理器,加分级,会话保持动态和退出。 此入口只装入 Web 标准和 jose,所以它运行 在节点18+,德诺,邦和工人.

import { createRelyingParty } from '@atlasauth/backend/rp';

const rp = createRelyingParty({
  issuer: process.env.ATLAS_ISSUER!,
  clientId: process.env.ATLAS_CLIENT_ID!,
  clientSecret: process.env.ATLAS_CLIENT_SECRET!,
  redirectUri: 'https://app.example.com/auth/callback',
  secret: process.env.SESSION_SECRET!,      // 32+ characters
  secretKey: process.env.ATLAS_SECRET_KEY,  // for touchIfDue and server-side revoke
  postLogoutRedirectUri: 'https://app.example.com/',
});

const startResponse = await rp.start(request);                  // 302 to /oauth2/authorize
const { user, tokens, response } = await rp.callback(request);  // user.sub, user.sid, user.auth_time, ...
const stepUp = await rp.requireRecentAuth(request, 300, { sub: user.sub, authTime: user.auth_time }); // null or a 302
const touch = await rp.touchIfDue(user.sid!, lastTouchedAt);    // 'skipped' | 'touched' | 'ended'
const signOut = await rp.signOut({ idToken: tokens.id_token, sid: user.sid });

或用一条线将三条路线都接通 rp.handle(request) (签名、召回和退出); null 对任何其他路径,给予 onSignedIn(user, tokens, request, result) 财务报告和审定财务报表 onSignOut(request) 在配置中. forwardParams: ['ref'] 将您登录的 URL 的额外查询参数传递到 /oauth2/authorize。 。 。 .

还出口 LogoutTokenVerifier, (中文(简体) ), fetchBackchannelLogout 财务报告和审定财务报表 verifyAtlasWebhook 编号 : verifyAtlasWebhookRequest (网易密码).

同一切入点单独公布于 @atlasauth/rp,其唯一依赖是 jose。 。 。 .

Next.js (App路由器) (英语)

// app/auth/login/route.ts
import { oidc } from '@atlasauth/backend';
import { config } from '@/lib/atlas';

export async function GET() {
  const { url, cookie } = await oidc.start(config);
  return new Response(null, { status: 302, headers: { location: url, 'set-cookie': cookie } });
}

// app/auth/callback/route.ts
import { oidc, AtlasOidcError } from '@atlasauth/backend';
import { config } from '@/lib/atlas';

export async function GET(request: Request) {
  try {
    const user = await oidc.callback({
      ...config,
      clientSecret: process.env.ATLAS_CLIENT_SECRET!,
      query: request.url,
      cookie: request.headers.get('cookie'),
    });
    const headers = new Headers({ location: '/' });
    headers.append('set-cookie', user.clearCookie);
    headers.append('set-cookie', await createAppSession(user)); // your own session cookie
    return new Response(null, { status: 302, headers });
  } catch (error) {
    if (!(error instanceof AtlasOidcError)) throw error;
    return new Response(`Sign-in failed (${error.code})`, {
      status: 400,
      headers: error.clearCookie ? { 'set-cookie': error.clearCookie } : {},
    });
  }
}

快递

import express from 'express';
import { oidc, AtlasOidcError } from '@atlasauth/backend';

const app = express();

app.get('/auth/login', async (req, res) => {
  const { url, cookie } = await oidc.start(config);
  res.append('Set-Cookie', cookie).redirect(url);
});

app.get('/auth/callback', async (req, res) => {
  try {
    const user = await oidc.callback({
      ...config,
      clientSecret: process.env.ATLAS_CLIENT_SECRET!,
      query: req.originalUrl,
      cookie: req.headers.cookie,
    });
    res.append('Set-Cookie', user.clearCookie);
    await startYourSession(req, res, user.namespacedSub);
    res.redirect('/');
  } catch (error) {
    if (!(error instanceof AtlasOidcError)) throw error;
    if (error.clearCookie) res.append('Set-Cookie', error.clearCookie);
    res.status(400).send(`Sign-in failed (${error.code})`);
  }
});

测试离线

createTestIssuer() 薄荷会话令牌和带有丢出密钥的ID令牌及其 fetch 服务于发现, JWKS 和一个符号端点, 因此一个完全的重定向的登录在没有网络的单元测试中运行 .

import { createTestIssuer, oidc, TEST_TOKEN_TAMPERS } from '@atlasauth/backend';

const fx = await createTestIssuer({ clientSecret: 'test-secret' });
const config = { issuer: fx.issuer, clientId: fx.clientId, redirectUri: 'https://app.example.com/auth/callback', secret: '…32+ chars…', fetchImpl: fx.fetch };

const { url, cookie } = await oidc.start(config);
const { callbackUrl } = await fx.authorize(url, { sub: 'user_1', claims: { email: '[email protected]', email_verified: true } });
const user = await oidc.callback({ ...config, clientSecret: 'test-secret', query: callbackUrl, cookie });

// Negative cases: wrong_aud, wrong_iss, foreign_key, expired, wrong_nonce, alg_none.
for (const tamper of TEST_TOKEN_TAMPERS) {
  const { url, cookie } = await oidc.start(config);
  const { callbackUrl } = await fx.authorize(url, { tamper });
  await expect(oidc.callback({ ...config, clientSecret: 'test-secret', query: callbackUrl, cookie })).rejects.toMatchObject({ code: 'invalid_token' });
}

fx.mintIdToken(claims, { aud, nonce, tamper }) 财务报告和审定财务报表 fx.mintSession(claims, { tamper }) 薄荷单币. @atlasauth/testing 再出口 createTestIssuer。 。 。 .

管理客户端( 密钥)

import { createAtlasClient, paginate } from '@atlasauth/backend';

const atlas = createAtlasClient({ secretKey: process.env.ATLAS_SECRET_KEY! });

const user = await atlas.users.create({ email_address: '[email protected]' });
for await (const u of paginate(atlas.users.list)) console.log(u.id);

每个后端API操作都有方法,按资源分组. 这些团体是:

区域命名空间
用户和会话users, (中文(简体) ), emailAddresses, (中文(简体) ), sessions, (中文(简体) ), signInTokens, (中文(简体) ), actorTokens
组织organizations, (中文(简体) ), invitations
作用和授权roles, (中文(简体) ), permissions, (中文(简体) ), fga, (中文(简体) ), openfga
访问和注册控制waitlist, (中文(简体) ), allowlist, (中文(简体) ), blocklist, (中文(简体) ), domains, (中文(简体) ), redirectUrls
实例设置instance, (中文(简体) ), instanceSecurity, (中文(简体) ), design, (中文(简体) ), branding, (中文(简体) ), localizations
模板和信件emailTemplates, (中文(简体) ), smsTemplates, (中文(简体) ), messaging, (中文(简体) ), notifications
社会和企业注册oauthProviders, (中文(简体) ), connectors, (中文(简体) ), ssoConnections, (中文(简体) ), ssoOnboarding, (中文(简体) ), orgLdapConnections, (中文(简体) ), scimTokens, (中文(简体) ), scimProvisioning, (中文(简体) ), legacyAuthConnection
证书和标志oauthClients, (中文(简体) ), resourceServers, (中文(简体) ), jwtTemplates, (中文(简体) ), apiKeys, (中文(简体) ), machines, (中文(简体) ), tokens, (中文(简体) ), sessionExchangeApps, (中文(简体) ), tickets
网络选择和审计webhooks, (中文(简体) ), auditLogs, (中文(简体) ), auditEvents, (中文(简体) ), events, (中文(简体) ), usage, (中文(简体) ), configChangelog, (中文(简体) ), logStreams
警卫attackProtection, (中文(简体) ), rateLimitPolicy, (中文(简体) ), riskBasedMfa, (中文(简体) ), botSignals, (中文(简体) ), captcha, (中文(简体) ), networkAcls, (中文(简体) ), managedWaf, (中文(简体) ), radiusClients
账单和应享权利billing, (中文(简体) ), entitlementOverrides, (中文(简体) ), organizationSettingsSchema
隐私dataSubjectRequests, (中文(简体) ), importExport
代理人和整编人员actions, (中文(简体) ), pca, (中文(简体) ), mcp, (中文(简体) ), mcpConnector, (中文(简体) ), ltiPlatforms
开发工具testMode, (中文(简体) ), diagnostics

检查您的设置

const report = await atlas.instance.preflight();
for (const f of report.findings) console.log(f.severity, f.message, '→', f.fix);

atlas.doctor() (或 npx atlas-doctor 与 ATLAS_SECRET_KEY set)检查密钥,时钟 skew对Atlas,后端API,JWKS,服务状态和实例配置,并从未丢出:

const report = await atlas.doctor();
if (!report.ok) console.error(formatDoctorReport(report));

退缩和可观察性

429(任何方法)或503(GET、PUT、DELETE、或带有一等钥匙的书写)两次重试,等待 Retry-After 或与焦躁的指数后退。 给它 retry: { maxRetries, baseDelayMs, maxDelayMs } 或通过 retry: false; 当服务器要求等待的时间比 maxDelayMs,则 AtlasApiError 被抛出 retryAfterMs 设置.

createAtlasClient({ secretKey, otel: true }) 记录每个请求的客户间距+ atlas.client.requests, (中文(简体) ), atlas.client.retries 财务报告和审定财务报表 atlas.client.request.duration通过全球 @opentelemetry/api (可选的对等依赖; 未设置, 没有加载) 。 过 otel: { tracer, meter } 用你自己的东西.

设计登录页面

import { readFile } from 'node:fs/promises';

const logo = await atlas.design.assets.upload(await readFile('logo.png'), {
  kind: 'logo',
  contentType: 'image/png',
});
await atlas.design.update({
  palette: { primary: '#4f46e5' },
  logo: { assetId: logo.asset_id },
});
// Per-organization override; null removes a setting.
await atlas.design.organizations.update('org_123', { palette: { primary: null } });

上传发送带有内容类型的原始图像字节 。 A 类 Blob 带来自己的类型.

API 密钥支出上限

给终端用户 API 每个单位和期间的密钥封顶(request, (中文(简体) ), second, (中文(简体) ), minute, (中文(简体) ), day, (中文(简体) ), month 或者说 total· ; { unit: 'requests', amount: 600, period: 'minute' } 每笔电话费的限额是每笔电话费的分数。 超过上限的借项被拒绝而不记录任何情况;它与 allowed: false 而不是扔东西.

const key = await atlas.apiKeys.create({
  subject_type: 'user',
  subject_id: 'user_123',
  limits: [
    { unit: 'requests', amount: 1000, period: 'day' },
    { unit: 'usd_cents', amount: 5000, period: 'month' },
  ],
});

const debit = await atlas.apiKeys.recordUsage(key.id, {
  unit: 'usd_cents',
  amount: 12,
  idempotency_key: 'req_8f2c', // a retry returns the first outcome without counting again
});
if (!debit.allowed) {
  // debit.code === 'LIMIT_EXCEEDED'; debit.exceeded lists the caps that refused it
}

const { usage } = await atlas.apiKeys.getUsage(key.id); // used / remaining / resets_at per cap
await atlas.apiKeys.update(key.id, { limits: null }); // remove the caps

apiKeys.verify(secret) 返回密钥 name, (中文(简体) ), scopes, (中文(简体) ), effective_scopes (通过实例范围的影响加以扩大), constraints, (中文(简体) ), limits 和当前 usage。 。 。 .

镜像 Atlas 数据输入您的数据库

createMirror 遵循更改种子(先是snapshot,然后是命令更改,在您的位置被修剪后自动重拍)并写入内存, Redis, Postgres 或您自己的 Drizzle 或 Prisma 表格(@atlasauth/backend/mirror/drizzle, (中文(简体) ), @atlasauth/backend/mirror/prisma) (中文(简体) ).

import { createMirror, memoryMirrorStore } from '@atlasauth/backend';

const mirror = createMirror({ client: atlas, resources: ['user', 'organization'], store: memoryMirrorStore() });
await mirror.sync(); // or mirror.start() to poll

本地 API 密钥镜像

createKeyMirror 保留您自己的表格权威: 薄荷和在阿特拉斯的导入, 对照sha256进行本地验证, 没有网络呼叫, 本地先取消, 并提取从其它处提取的撤销信息 .

import { createKeyMirror, memoryKeyMirrorStore } from '@atlasauth/backend';

const mirror = createKeyMirror({ store: memoryKeyMirrorStore(), apiKeys: atlas.apiKeys });
const { secret } = await mirror.mint({ subject_type: 'organization', subject_id: 'org_123', scopes: ['calls:write'] });
const result = await mirror.verify(presented, { requiredScopes: ['calls:write'] }); // local, no network call
await mirror.revoke(keyId); // local first, then Atlas
setInterval(() => void mirror.sync(), 15_000); // revocation feed
// await mirror.applyWebhook(event) for verified api_key.* webhooks

回来 KeyMirrorStore (单位:千美元)put, (中文(简体) ), get, (中文(简体) ), getByHash, (中文(简体) ), markRevoked, (中文(简体) ), getCursor, (中文(简体) ), setCursor)与自制的桌子.

会话保存

如果您的应用程序只在某些页面上加载了Atlas的前端SDK,则在其他页面上所花的时间可以算作是无活性. 从后端触摸会话以滑动其不活动窗口 :

const s = await atlas.sessions.touch(sessionId);
// s.idle_expires_at is the new inactivity deadline; s.extended is false when a touch in the last minute already did it

触摸从未延续过会话的绝对到期,也从未恢复过会话的结束( )409 SESSION_REVOKED, (中文(简体) ), SESSION_IDLE_EXPIRED 或者说 SESSION_EXPIRED) (中文(简体) ). 它每分钟写一次 所以每次请求都安全.

严格的请求机构

默认情况下, Atlas 忽略了一个实体字段, 端点不接受 。 它列出在 Atlas-Unknown-Fields 响应头.

const atlas = createAtlasClient({
  secretKey,
  strict: true, // refuse unknown fields with 422 UNKNOWN_FIELD instead
  onUnknownFields: ({ method, path, fields }) => console.warn(method, path, 'ignored', fields),
});

// Override for some calls:
await atlas.withOptions({ strict: false }).users.update('user_123', body);

错误

每次失败的调用 AtlasApiError,已经 status, (中文(简体) ), code 和全文 errors 列表中选择一个选项. AtlasErrorCodes 名称 :

import { AtlasApiError, AtlasErrorCodes } from '@atlasauth/backend';

try {
  await atlas.notifications.send(body);
} catch (e) {
  if (e instanceof AtlasApiError && e.hasCode(AtlasErrorCodes.EMAIL_DELIVERY_NOT_CONFIGURED)) {
    // set up an email provider first
  }
}

平台客户端( 平台密钥)

import { createAtlasPlatformClient } from '@atlasauth/backend';

const platform = createAtlasPlatformClient({ platformKey: process.env.ATLAS_PLATFORM_KEY! });

const app = await platform.apps.create({ name: 'Acme' });
const signIn = await platform.atlasIdApps.create({
  name: 'Acme',
  redirect_uris: ['https://acme.example/callback'],
  post_logout_redirect_uris: ['https://acme.example/'],
  backchannel_logout_uri: 'https://acme.example/backchannel-logout',
  branding: { theme: 'dark', accent_color: '#4f46e5' },
});
console.log(signIn.client_id, signIn.client_secret); // the secret is shown once

平台客户端有这些命名空间:

  • apps: 创建、更新、存档、传输、主张和删除应用程序、管理其实例、旋转和列出机密
  • accountlessApps
  • atlasIdApps: 与 Atlas 应用签名,包括设计预览和秘密旋转
  • designAssets

它接受同样的 strict, (中文(简体) ), onUnknownFields 财务报告和审定财务报表 withOptions 设置.

域名移动、 登出扇并管理电子邮件

// Move every URL from the old origin to the new one. dry_run first shows the diff.
const preview = await atlas.instance.moveOrigin({ from: 'https://old.example.com', to: 'https://example.com', dry_run: true });
for (const c of preview.changes) console.log(c.object, c.field, c.before, '->', c.after);

// Send the same logout_token your OIDC apps get to another service.
await atlas.logoutNotifyEndpoints.create({ url: 'https://api.example.com/atlas/logout' });

// Send email from your own domain through Atlas, then publish the returned DNS records.
const managed = await atlas.messaging.managedEmail.create({ from_address: 'Acme <[email protected]>' });
console.log(managed.records);

在平台客户端上, atlasIdApps.findUsers(id, email) 回答一个应用程序的隐私请求, atlasIdApps.revokeGrant(id, grantId) 撤销一个人的许可 atlasIdPublisher 索赔和核实 在同意屏幕上显示的出版商名称.

后通道登录

当用户在Atlas系统中签名时, Atlas POSTs a logout_token 给您的 backchannel_logout_uri。在本地会话结束前进行校验 :

import { LogoutTokenVerifier, memoryLogoutReplayStore } from '@atlasauth/backend';

const logoutTokens = new LogoutTokenVerifier({
  issuer: 'https://atlasauth.net', // or your instance's issuer
  clientId: process.env.ATLAS_CLIENT_ID!,
  replayStore: memoryLogoutReplayStore(), // use a shared store across processes
});

// Framework-agnostic handler: (form body) -> { status, headers }
export async function handleBackchannelLogout(form: URLSearchParams) {
  const headers = { 'Cache-Control': 'no-store' };
  const token = form.get('logout_token');
  if (!token) return { status: 400, headers };

  const result = await logoutTokens.verify(token);
  if (!result.ok) {
    // retryable: our side failed for now (the issuer's keys could not be fetched).
    // 503 + Retry-After makes Atlas send it again; 400 is final and never retried.
    return result.retryable
      ? { status: 503, headers: { ...headers, 'Retry-After': '30' } }
      : { status: 400, headers };
  }

  const { sid, sub } = result.claims;
  if (sid) await sessions.deleteByAtlasSid(sid); // your session store
  else if (sub) await sessions.deleteAllForUser(sub);
  return { status: 200, headers };
}

核查员检查如下:

  • rS256 签名
  • iss,该 aud 包含您的客户端 id
  • iat,它必须是存在的, 不在未来和不太老 exp 如果有的话
  • jti
  • 后通道登录事件
  • 那个 sid 或者说 sub 当前
  • 没有 nonce
  • 那个 typ 实值 logout+jwt,当标题有一个

对于由您自己签名的标志, new AtlasBackend({ issuer }).verifyLogoutToken(token, { clientId }) 重用实例缓存的密钥 .

要拒绝在多个服务器上重放, 请执行 LogoutReplayStore.remember(jti, expiresAtMs) 在一个共享的商店,例如 Redis SET NX PX。 。 。 .

阿特拉斯重审一个得到的交付 5xx, (中文(简体) ), 408, (中文(简体) ), 429 5秒5分30分2小时5小时10小时后 任何其他 4xx 最终决定。 阿特拉斯系统从未遵循过方向:a 3xx 答复记录为 backchannel_redirect_not_followed因此,在域移动更新后 backchannel_logout_uri 到最后的URL。 回答自己的短暂失败( 数据库已下调) 503 我也一样.

要检查路线终点到终点, oauthClients.testBackchannelLogout(id) 发送一个签名的测试令牌( s)sid 开始 sid_test_,额外索赔 atlas_test: true并返回所回答的路线; oauthClients.listBackchannelDeliveries(id) 列出每次尝试.

网络呼号

verifyWebhook 请检查Atlas发送到您的 Webhook 端点的事件的签名 。 您管理端点 atlas.webhooks.endpoints: 创建,更新,揭示或旋转一个秘密,发送测试事件,并重报过去交付.

这个 atlas-signature 信头可以携带一个以上的签名,以空格分隔(v1,<sig> v1,<sig>) (中文(简体) ). 虽然一个端点秘密会旋转,但阿特拉斯可能会用旧的和新的秘密在每次交货上签名. verifyWebhook 当任何一种签名符合您的秘密时, 接受发送.

网络加密上的标准 Webhooks

您的应用程序通过 Atlas 发布的事件遵循标准 Webhooks . verifyStandardWebhookWeb({ body, headers, secret }) (和 verifyStandardWebhookRequest(request, { secret }))仅用Web Crypto验证它们,因此它们运行在Workers,Deno,Bun以及浏览器和节点上. signAtlasWebhook 财务报告和审定财务报表 signStandardWebhook 从 @atlasauth/rp/testing 制作正确的签名 交付给您的接受者测试.

类型事件

verifyWebhook 返回一个 AtlasWebhookEvent,一个工会结束 type,所以一个 switch 缩进 data数字 :

const { verified, event } = verifyWebhook({ body: rawBody, headers, secret });
if (!verified || !event) return res.sendStatus(400);
switch (event.type) {
  case 'user.created':
    await createUser(event.data.id, event.data.primary_email_address);
    break;
  case 'organization_membership.updated':
    console.log(event.data.role, event.data.role_source); // RoleSource | null
    break;
  default:
    // an event type newer than this package version
}

ATLAS_WEBHOOK_EVENT_TYPES 列出每一种类型. RoleSource 实值 "manual", (中文(简体) ), "scim", (中文(简体) ), "directory_sync", (中文(简体) ), "sso:<connection id>" 或者说 "ldap:<connection id>"。 。 。 .

将用户映射到数据库

syncLocalUser 为已签名的Atlas用户查找或创建行。 校验会话后拨打:

const { user, created, linked } = await syncLocalUser(auth.claims, {
  findBySub: (sub) => db.user.findUnique({ where: { atlasSub: sub } }),
  findByEmail: (email) => db.user.findUnique({ where: { email } }),
  subOf: (row) => row.atlasSub,
  isLocalEmailVerified: (row) => row.emailVerified,
  link: (row, sub) => db.user.update({ where: { id: row.id }, data: { atlasSub: sub } }),
  create: ({ sub, email, email_verified, external_id }) =>
    db.user.create({ data: { atlasSub: sub, email, emailVerified: email_verified, externalId: external_id } }),
});
  • 发现一行 sub 先说.
  • 已有行只在符号上注明时通过电子邮件链接 email_verified: true 和 isLocalEmailVerified(row) 这是真的。 如果将一个地址联系起来,没有人证明,那么登记地址的任何人都会先记账.
  • 已经绑定到不同 Atlas 用户的行永远不会重新连接 : AtlasLinkConflictError 被抛出.
  • 在 Atlas 子上设置一个独特的索引 。 当两个先要求比赛,失败者 create 失败了 syncLocalUser 返回胜利者的行. isDuplicateError 决定何为失败( 默认: Postgres) 23505,或包含"重复"或"独特"的信息.

信物携带 email, (中文(简体) ), email_verified 财务报告和审定财务报表 external_id 当实例在会话符中包含电子邮件请求时.

需要外交部

if (!mfaSatisfied(auth.claims)) return res.status(403).json({ error: 'mfa_required' });

mfaSatisfied 信物上写着的是真的 mfa_satisfied: true,或者当 mfa 没错, acr 实值 urn:atlas:acr:mfa 或者说 amr 包含 mfa,或会议通过企业SSO(sso_connection_id) (中文(简体) ). usedMultiFactor 财务报告和审定财务报表 authenticatedWithin 带 SessionClaims 我也一样.

一个组织外务省的政策是单独的一条条: mfaOrgSatisfied(auth.claims, orgId) 当组织不需要外交部时,或标志上写着 org_mfa_ok. 保险单只扣下组织的债权,因此从未读过的申请不会被停止;从自己的表格中授权的应用程序必须调用 mfaOrgSatisfied 自己来 它只描述该标志的活跃组织;对于其他任何组织,用户属于,呼叫 client.organizations.mfaStatus(orgId, { sessionId })。 。 。 .

中国证监会

对于饼干认证表和 fetch 电话:

const secret = process.env.ATLAS_CSRF_SECRET ?? deriveCsrfSecret(process.env.ATLAS_SECRET_KEY!);
const token = csrfToken({ sid: auth.claims.sid, secret }); // set as the __atlas_csrf cookie, render into the page
// on a POST:
if (!(await verifyCsrf(request, { sid: auth.claims.sid, secret }))) return new Response(null, { status: 403 });

符号绑定在会话上(sid) (中文(简体) ). verifyCsrf 从 x-atlas-csrf 标题或一个 atlas_csrf 构成字段,它必须等于 __atlas_csrf 饼干 形式体是从克隆人读取的,所以你的处理器仍然可以读取. 和下一个Js, atlasProxy({ csrf: true }) 内 @atlasauth/nextjs 做这一切为你.

移交归属

随着转诊归属的开始,阿特拉斯系统在您转诊平台的每次互动注册之前都会报告 user.created 发送。 答案落在用户身上: referral (他们已回避他们,或已回避), referral_code 财务报告和审定财务报表 referral_link (用户自己的代码和链接,当他们是一个裁判).

await atlas.referrals.update({
  enabled: true,
  provider: 'webhook', // the Atlas referral protocol; or a built-in vendor adapter
  endpoint_url: 'https://referrals.example.com/atlas',
  signing_secret: process.env.REFERRAL_SIGNING_SECRET,
  auto_enroll_users: true,
  link_template: 'https://app.example.com/join?ref={code}',
  security: { on_high_risk: 'hold' },
});
const test = await atlas.referrals.test(); // { ok, http_status, latency_ms, error? }
const failed = await atlas.referrals.listCalls({ status: 'failed', limit: 20 });
await atlas.referrals.retryCall(failed.data[0]!.id);

const user = await atlas.users.get('user_123');
user.referral?.referrer_user_id;
await atlas.users.update('user_123', { referral_code: 'ada-1' }); // 409 REFERRAL_CODE_TAKEN if held
await atlas.users.flagReferral('user_123', { reason: 'self-referral' });

浏览器抓取 : browser_sources 列出浏览器读取的脚本或 cookie 的推荐程序( 更新时不包含返回提供者的默认, 由 browser_sources_default),以及 consent_provider 选择决定(auto一个经理,或 app) (中文(简体) ).

无法满足供应商的需求 422 REFERRALS_NOT_CONFIGURED。当平台回复延迟时,阿特拉斯发送 user.referral_attributed 网络hook (UserReferralAttributedEvent) (中文(简体) ). 飞行前可报告 referrals_enabled_unverified 财务报告和审定财务报表 referrals_without_bot_protection。 。 。 .

接收推荐协议( 移交平台)

执行Atlas查询协议的查询平台与 verifyReferralRequest每一次通话都是 POST 它的JSON身体上写着它是什么行动op: signup, (中文(简体) ), erase, (中文(简体) ), export, (中文(简体) ), partner 或者说 signal。通过呼叫到达的路径:一个签名的机构,其 op 拒绝匹配终点(op_mismatch) (中文(简体) ).

import { verifyReferralRequest } from '@atlasauth/backend';

app.post('/atlas/:endpoint', express.raw({ type: '*/*' }), async (req, res) => {
  const { verified, call } = verifyReferralRequest({
    body: req.body,
    headers: req.headers,
    secret: process.env.REFERRAL_SIGNING_SECRET!,
    path: req.path, // or expectedOp: 'signup' on a handler for one endpoint
  });
  if (!verified || !call) return res.sendStatus(401);
  switch (call.op) {
    case 'signup': // ReferralSignupPayload → answer ReferralSignupResponse
      return res.json({ referral: await match(call.payload), partner: await enroll(call.payload) });
    case 'export': // the connection test sends test: true
      return call.test ? res.sendStatus(404) : res.json(await exportFor(call.payload.customerRef));
    default: // erase, partner, signal
      await handle(call);
      return res.sendStatus(204);
  }
});

一个电话的重试会保留它 atlas-id (单位:千美元)deliveryId) (中文(简体) ). 报名是独家的 customerRef:回答重试的方式相同.

双向同步

提供商与 inbound 能力可以将注册后所做的转录更改发回Atlas。 设置报告 inbound.url (交给平台), inbound.secret_set, (中文(简体) ), inbound.endpoint_id 财务报告和审定财务报表 inbound.last_event (单位:千美元){ at, type, outcome }) (中文(简体) ). 将平台事件保密 inbound_secret (只写字,16至512个字符) inbound_endpoint_id数字 :

await atlas.referrals.update({ inbound_secret: process.env.REFERRAL_INBOUND_SECRET, inbound_endpoint_id: 'we_123' });
const { inbound } = await atlas.referrals.get(); // inbound.last_event?.outcome: 'applied', 'stale', …

Atlas查询协议上的一个平台 referral.updated 事件为 inbound.url,如Atlas Webhook 与输入密件(或连接在未设置密件时是签名密件)一样签名. signReferralUpdatedEvent 构建它:

import { signReferralUpdatedEvent } from '@atlasauth/backend';

const e = signReferralUpdatedEvent({
  inboundUrl: settings.inbound.url,
  secret: process.env.REFERRAL_SIGNING_SECRET!,
  event: { customerRef: 'user_123', referralId: 'ref_1', status: 'qualified' },
});
await fetch(e.request());

版本与发布记录

@atlasauth/backend 共发布 71 个版本,首次发布于 2026/09/14;过去 12 个月发布了 71 个版本。

版本发布日期距今
0.55.0latest2026/11/16今天
0.54.02026/11/15今天
0.53.02026/11/15今天
0.52.02026/11/15今天
0.51.02026/11/151 天前
0.50.02026/11/151 天前

关于 @atlasauth/backend 的常见问题

@atlasauth/backend 是什么?

用于节点、德诺、邦和边缘运行时间的服务器侧图集。 它有三样东西:

如何安装 @atlasauth/backend?

在项目目录运行 npm install @atlasauth/backend,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 atlas-doctor,可以用 npx atlas-doctor 直接运行。

@atlasauth/backend 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@atlasauth/backend 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@atlasauth/backend 可以商用吗?

@atlasauth/backend 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@atlasauth/backend 的下载量有多少?

根据 npm 官方下载统计,@atlasauth/backend 在 2026/10/07 – 2026/11/05 这 30 天内被下载 7,536 次,日均约 251 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@atlasauth/backend 还在维护吗?

最新版本 0.55.0 发布于 2026/11/16(今天);至今共发布 71 个版本,其中过去 12 个月发布了 71 个;首次发布于 2026/09/14。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。