npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@1meeting/auth-client-sdk

v0.2.0

Published

HighGood 认证中心 OAuth2 + PKCE 客户端:Node 机密客户端与浏览器公钥(PKCE)

Readme

@1meeting/auth-client-sdk

面向业务应用(A/B/未来扩展应用)的统一登录接入 SDK。
封装了平台仓 OAuth2 授权码 + PKCE 常用能力,帮助应用快速完成“跳转授权 -> 回调换码 -> 拉取用户信息”闭环。

快速上手请先看:QUICKSTART.md

仓库内体系化集成说明(推荐)../../docs/05-client-sdk.md(流程、环境、authApiRoots、双入口、安全清单)。
全仓文档索引:../../docs/README.md


1. 适用场景

  • 子应用不自建账号体系,接入平台仓统一登录
  • OAuth2 授权码模式(response_type=code
  • 支持 Node 后端(推荐)和浏览器 PKCE 场景

2. 安装

2.1 从 npmjs 安装(推荐)

npm i @1meeting/auth-client-sdk

公开包可直接从 npmjs 安装,无需 GitHub 私有 registry token。

2.2 本地源码构建(本仓)

npm run build --prefix packages/auth-client-sdk

3. 导出接口总览

Node 入口:@1meeting/auth-client-sdk

  • buildOAuth2AuthorizeUrl(input)(可选 scope
  • buildOAuth2LogoutRedirectUrl(input)
  • createNodeCodeVerifier()
  • createS256CodeChallenge(verifier)
  • createNodePkcePair()
  • exchangeAuthorizationCode(input)(可选 signal
  • exchangeRefreshTokenWithRoots(input)
  • fetchUserInfoWithRoots(input)(可选 signal
  • pickAccessTokenFromTokenResponse / pickRefreshTokenFromTokenResponse / pickExpiresInFromTokenResponse / pickTokenTypeFromTokenResponse
  • parseTokenFromResponse(payload)getApiErrorFromPayload(payload)
  • pickUserInfoFromUserinfoResponse(payload)
  • 别名兼容:
    • createCodeVerifier(同 createNodeCodeVerifier
    • createCodeChallenge(同 createS256CodeChallenge

浏览器入口:@1meeting/auth-client-sdk/browser

  • 与 Node 入口对齐的 URL / 换码 / userinfo / 解析辅助函数(同上)
  • createBrowserCodeVerifier()createS256CodeChallenge(verifier)(异步)、createBrowserPkcePair()(异步)
  • exchangeAuthorizationCodePublic(input)(无 clientSecret 的换码封装)

4. 标准接入流程(推荐:后端换码)

  1. 服务端生成 state + PKCE(codeVerifier/codeChallenge
  2. 组装授权地址并跳转平台仓 /api/oauth2/authorize
  3. 回调拿到 code 后,服务端调用 exchangeAuthorizationCode
  4. 从 token 响应中解析 accessToken
  5. fetchUserInfoWithRoots 获取用户信息并建立本地会话

5. Node 接入示例(Express/BFF)

import crypto from 'node:crypto'
import {
  createNodePkcePair,
  buildOAuth2AuthorizeUrl,
  exchangeAuthorizationCode,
  fetchUserInfoWithRoots,
  getApiErrorFromPayload,
  parseTokenFromResponse,
  pickAccessTokenFromTokenResponse,
  pickUserInfoFromUserinfoResponse,
} from '@1meeting/auth-client-sdk'

// Step 1: 生成授权地址
const { codeVerifier, codeChallenge } = createNodePkcePair()
const state = crypto.randomUUID()

// 你需要把 codeVerifier 按 state 暂存到 session/redis
// saveState(state, { codeVerifier })

const authorizeUrl = buildOAuth2AuthorizeUrl({
  authPublicBaseUrl: 'https://www.highgood.com/auth',
  clientId: 'client_app_a',
  redirectUri: 'https://www.highgood.com/a/api/auth/callback',
  state,
  nonce: crypto.randomUUID(),
  codeChallenge,
  codeChallengeMethod: 'S256',
})

// Step 2: 回调换码
const tokenResult = await exchangeAuthorizationCode({
  code, // callback query.code
  codeVerifier, // 从你保存的 state 中取回
  redirectUri: 'https://www.highgood.com/a/api/auth/callback',
  clientId: 'client_app_a',
  clientSecret: 'app-a-secret',
  authApiRoots: [
    'http://auth-api.internal:3001', // 首选内网
    'https://www.highgood.com/auth', // 可选 fallback
  ],
})

if (!tokenResult.ok) {
  const err = getApiErrorFromPayload(tokenResult.payload)
  throw new Error(err?.message || `token exchange failed: ${tokenResult.status}`)
}

const token = parseTokenFromResponse(tokenResult.payload)
const accessToken = token?.accessToken ?? pickAccessTokenFromTokenResponse(tokenResult.payload)
if (!accessToken) {
  throw new Error('token payload missing accessToken')
}

// Step 3: 获取用户信息
const userInfoResult = await fetchUserInfoWithRoots({
  accessToken,
  authApiRoots: ['http://auth-api.internal:3001'],
})

if (!userInfoResult.ok) {
  throw new Error(`userinfo failed: ${userInfoResult.status}`)
}

const user = pickUserInfoFromUserinfoResponse(userInfoResult.payload)
if (!user?.sub) {
  throw new Error('userinfo payload invalid')
}

6. 浏览器 PKCE 示例(纯前端)

import {
  createBrowserPkcePair,
  buildOAuth2AuthorizeUrl,
  exchangeAuthorizationCodePublic,
} from '@1meeting/auth-client-sdk/browser'

const { codeVerifier, codeChallenge } = await createBrowserPkcePair()
const state = crypto.randomUUID()
sessionStorage.setItem(`pkce:${state}`, codeVerifier)

const authorizeUrl = buildOAuth2AuthorizeUrl({
  authPublicBaseUrl: 'https://www.highgood.com/auth',
  clientId: 'client_app_a',
  redirectUri: 'https://www.highgood.com/a/callback',
  state,
  nonce: crypto.randomUUID(),
  codeChallenge,
  codeChallengeMethod: 'S256',
})

location.href = authorizeUrl

// 回调页中:
const code = new URL(location.href).searchParams.get('code')!
const callbackState = new URL(location.href).searchParams.get('state')!
const verifier = sessionStorage.getItem(`pkce:${callbackState}`)!

const tokenResult = await exchangeAuthorizationCodePublic({
  code,
  codeVerifier: verifier,
  redirectUri: 'https://www.highgood.com/a/callback',
  clientId: 'client_app_a',
  authApiRoots: ['https://www.highgood.com/auth'],
})

安全建议:生产优先用后端换码(BFF),不要在浏览器暴露机密 clientSecret


7. API 详细说明

7.1 buildOAuth2AuthorizeUrl(input)

构建平台仓授权地址,返回完整 URL 字符串。

必填参数:

  • authPublicBaseUrl:认证中心对浏览器公开入口(如 https://www.highgood.com/auth
  • clientId
  • redirectUri
  • state
  • nonce
  • codeChallenge
  • codeChallengeMethod:固定 S256

可选参数:

  • responseType:默认 code
  • scope:空格分隔(如 openid profile);与 extraQuery.scope 同时存在时 scope 为准
  • extraQuery:附加 query 参数

7.2 createNodePkcePair() / createBrowserPkcePair()

生成 { codeVerifier, codeChallenge },用于 PKCE 流程。

  • Node 版同步
  • Browser 版异步(WebCrypto)

7.3 exchangeAuthorizationCode(input)

调用认证中心 /api/oauth2/token 执行换码,返回:

type HttpResult = {
  ok: boolean
  status: number
  payload: unknown
}

特点:

  • authApiRoots 支持多地址顺序尝试
  • 仅当 fetch 抛网络级异常时才尝试下一个 root
  • 一旦拿到 任意 HTTP 响应(含 4xx/5xx)即返回(不重复消费授权码;也不为 5xx 自动换根以免语义混乱)
  • 可选 signalAbortController 超时/取消

7.4 exchangeRefreshTokenWithRoots(input)

POST /api/oauth2/token,JSON:grantType: 'refresh_token'refreshTokenclientIdclientSecret
多根与 signal 行为同 exchangeAuthorizationCode


7.5 fetchUserInfoWithRoots(input)

调用认证中心 /api/oauth2/userinfo 获取用户信息,返回 HttpResult(同上)。支持 signal


7.6 buildOAuth2LogoutRedirectUrl(input)

拼浏览器顶层导航用的 GET …/api/oauth2/logout-redirect?return_to=…&global=1global 可选)。


7.7 parseTokenFromResponse / pick* / getApiErrorFromPayload

  • parseTokenFromResponse:成功包络下返回 TokenDataaccessTokenrefreshTokentokenTypeexpiresIn),否则 null
  • pickRefreshTokenFromTokenResponse 等:按需取单字段
  • getApiErrorFromPayload:从失败包络取 code / message / traceIdcode === 'OK' 时返回 null

7.8 pickUserInfoFromUserinfoResponse(payload)

从统一包络提取用户信息:

  • payload.code === 'OK' 且存在 data.sub,返回 UserInfoData
  • 否则返回 undefined

8. 类型定义(核心)

  • HighgoodApiEnvelope<T>
  • TokenData
  • UserInfoData
  • HttpResult
  • BuildAuthorizeUrlInput
  • BuildLogoutRedirectUrlInput
  • ExchangeCodeInput
  • ExchangeRefreshTokenInput
  • FetchUserInfoInput

详见:src/types.ts


9. 常见问题

Q1: ERR_CONNECTION_REFUSED / token 请求失败

  • 先检查 authApiRoots 是否可达(内网地址优先)
  • 检查平台仓服务是否已启动

Q2: 回调后提示 state 不合法

  • 确认 state -> codeVerifier 保存与回调读取是同一份存储(session/redis)
  • 确认 state 未过期(建议 10~15 分钟 TTL)

Q3: redirect_uri 不匹配

  • 确认 SDK 传入 redirectUri 与平台注册 OAuth 客户端配置完全一致(协议/域名/路径)

Q4: 浏览器端换码失败

  • 检查是否错误使用了机密客户端
  • 建议改为后端(BFF)换码

Q5: 换码 / userinfo 请求挂起

  • exchangeAuthorizationCode / fetchUserInfoWithRoots / exchangeRefreshTokenWithRoots 传入 signal(例如 AbortSignal.timeout(8000),按运行时支持情况 polyfill)

10. 发布建议

  • 版本号遵循 semver(0.1.x 修复,0.2.x 新能力);变更见 CHANGELOG.md
  • 发布前至少验证:
    • 授权跳转
    • 回调换码
    • userinfo
    • 本地 loopback(localhost/127.0.0.1)场景