@scorehub/auth-sdk
v1.5.1
Published
Auth-Service SDK for Node.js applications
Readme
@scorehub/auth-sdk 使用文档
安装
npm install @scorehub/auth-sdk初始化
import { AuthClient } from '@scorehub/auth-sdk';
const authClient = new AuthClient({
serviceUrl: 'https://api-test.scorehub.cn/auth/api/v1', // Auth-Service 地址
clientId: 'your-client-id', // OAuth Client ID
clientSecret: 'your-client-secret', // OAuth Client Secret
publicKey: fs.readFileSync('./keys/public.pem'), // RS256 公钥 (可选,用于本地验证)
issuer: 'auth-service', // JWT issuer,默认 'auth-service'
});
publicKey可选。提供时启用本地 JWT 验证(零网络延迟);不提供时需调用introspectToken远程验证。
获取公钥
服务提供标准 JWKS 端点,可在运行时动态获取公钥。
JWKS 地址:
GET https://api-test.scorehub.cn/auth/api/v1/.well-known/jwks.json动态获取并初始化(推荐)
import { AuthClient } from '@scorehub/auth-sdk';
async function createAuthClient() {
const res = await fetch('https://api-test.scorehub.cn/auth/api/v1/.well-known/jwks.json');
const { keys } = await res.json();
const x5c = keys[0].x5c[0];
// 还原为 PEM 格式
const publicKey = [
'-----BEGIN PUBLIC KEY-----',
...x5c.match(/.{1,64}/g),
'-----END PUBLIC KEY-----',
].join('\n');
return new AuthClient({
serviceUrl: 'https://api-test.scorehub.cn/auth/api/v1',
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
publicKey,
});
}
const authClient = await createAuthClient();提前下载保存为文件
curl -s https://api-test.scorehub.cn/auth/api/v1/.well-known/jwks.json \
| node -e "
const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
const x5c = d.keys[0].x5c[0];
const pem = '-----BEGIN PUBLIC KEY-----\n' + x5c.match(/.{1,64}/g).join('\n') + '\n-----END PUBLIC KEY-----';
process.stdout.write(pem);
" > public.pem然后直接读取文件初始化:
import fs from 'fs';
const authClient = new AuthClient({
serviceUrl: 'https://api-test.scorehub.cn/auth/api/v1',
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
publicKey: fs.readFileSync('./public.pem'),
});Token 验证
本地验证(推荐,需配置 publicKey)
try {
const payload = await authClient.verifyAccessToken(token);
console.log(payload.sub); // 用户 ID
console.log(payload.username); // 用户名
console.log(payload.roles); // 角色列表
console.log(payload.scopes); // 授权范围
} catch (err) {
// TokenExpiredError / JsonWebTokenError
}远程内省(无 publicKey 时使用)
const result = await authClient.introspectToken(token);
if (result.active) {
console.log(result.sub, result.username);
} else {
// token 已失效
}TokenPayload 结构
| 字段 | 类型 | 说明 |
|------|------|------|
| sub | string | 用户/Client ID |
| type | 'user' | 'client' | Token 类型 |
| username | string? | 用户名 |
| roles | string[]? | 角色列表 |
| clientId | string? | OAuth Client ID |
| scopes | string[] | 授权范围 |
| jti | string | JWT ID |
| iss | string | 签发者 |
| iat / exp | number | 签发/过期时间戳 |
用户认证
检查手机号是否已注册
const { exists } = await authClient.checkUserExists('13800138000');
if (exists) {
// 直接走登录流程
} else {
// 引导注册
}公开接口,无需鉴权。适合在注册/登录前预判手机号状态。
账号密码登录
const result = await authClient.loginByPassword('username', 'password');
console.log(result.accessToken);
console.log(result.refreshToken);
console.log(result.isNewUser); // false
console.log(result.user.id, result.user.roles);短信验证码登录(含自动注册)
// 先发送验证码
await authClient.sendSmsCode('13800138000');
// 验证码登录(手机号不存在时自动注册)
const result = await authClient.loginBySms('13800138000', '123456');
console.log(result.isNewUser); // true=本次自动注册,false=历史已存在用户注册
// 先发送验证码
await authClient.sendSmsCode('13800138000');
const result = await authClient.register({
username: 'testuser',
password: 'Password123',
mobile: '13800138000',
code: '123456',
realname: '张三', // 可选
});
console.log(result.isNewUser); // trueLoginResult 结构
| 字段 | 类型 | 说明 |
|------|------|------|
| accessToken | string | Access Token |
| refreshToken | string | Refresh Token |
| expiresIn | number | Access Token 有效期(秒) |
| tokenType | string | Token 类型,通常为 Bearer |
| isNewUser | boolean | true 表示本次请求中创建了新用户 |
| user | object | 当前登录用户信息 |
重置密码
await authClient.sendSmsCode('13800138000');
await authClient.resetPassword('13800138000', '123456', 'NewPassword123');修改密码(已登录)
await authClient.changePassword(
accessToken,
'OldPassword123',
'NewPassword123',
);该方法用于已登录用户使用旧密码修改为新密码,需要传入当前用户的 Access Token。
校验短信验证码(一次性)
await authClient.verifySmsCode('13800138000', '123456');
// 成功则继续,失败抛出异常Token 管理
刷新 Token
const tokenPair = await authClient.refreshToken(refreshToken);
// { access_token, refresh_token, token_type, expires_in }吊销 Token
await authClient.revokeToken(accessToken);服务间调用
获取 Service Token(自动缓存)
// client_credentials 方式,内置缓存,剩余有效期 < 60s 时自动刷新
const serviceToken = await authClient.getServiceToken(['read:users']);
fetch('http://other-service/api/data', {
headers: { Authorization: `Bearer ${serviceToken}` },
});根据 ID 查询用户信息
const user = await authClient.getUserById('user-uuid');
// { sub, username, mobile, email, realname, nickname, avatar, roles }管理端操作
使用 service token 鉴权,无需短信验证码,适合后台管理系统调用。
管理端创建用户
const user = await authClient.adminCreateUser({
username: 'newuser',
password: 'Password123',
mobile: '13900139000',
realname: '李四', // 可选
});
// { id, username, mobile }管理端重置用户密码
await authClient.adminResetPassword('user-uuid', 'NewPassword123');设置用户状态
// 将用户状态设为 DISABLED(禁用)
await authClient.setUserStatus('user-uuid', 'DISABLED');
// 将用户状态恢复为 ACTIVE(启用)
await authClient.setUserStatus('user-uuid', 'ACTIVE');
// 将用户状态设为 DELETED(软删除)
await authClient.setUserStatus('user-uuid', 'DELETED');状态枚举:
ACTIVE(正常)、DISABLED(禁用)、DELETED(已删除)。 使用 service token 鉴权,适合后台管理系统调用。
删除用户(软删除)
await authClient.deleteUser('user-uuid');软删除:将用户状态设为
DELETED,保留数据用于审计,不物理删除。等效于setUserStatus(userId, 'DELETED')。
管理端修改用户信息
// 修改用户名
await authClient.adminUpdateUser('user-uuid', { username: 'new_username' });
// 修改手机号
await authClient.adminUpdateUser('user-uuid', { mobile: '13900139001' });
// 修改真实姓名
await authClient.adminUpdateUser('user-uuid', { realname: '张三' });
// 同时修改多个字段
await authClient.adminUpdateUser('user-uuid', {
username: 'new_username',
mobile: '13900139001',
realname: '张三',
});使用 service token 鉴权,无需短信验证码,适合后台管理系统调用。
username和mobile唯一,冲突时抛出 409 错误(err.authCode对应业务错误码)。
框架中间件
Koa
import Koa from 'koa';
import { AuthClient, koaAuthMiddleware } from '@scorehub/auth-sdk';
const app = new Koa();
const authClient = new AuthClient({ /* ... */ });
app.use(koaAuthMiddleware(authClient, {
exclude: ['/health', '/api/v1/auth/login'], // 不需认证的路径
}));
// 路由中获取用户信息
app.use(async (ctx) => {
const user = ctx.state.user; // TokenPayload
const admin = ctx.state.admin; // 兼容旧代码: admin._id / admin.username
});Express
import express from 'express';
import { AuthClient, expressAuthMiddleware } from '@scorehub/auth-sdk';
const app = express();
const authClient = new AuthClient({ /* ... */ });
app.use(expressAuthMiddleware(authClient, {
exclude: ['/health'],
}));
// 路由中获取用户信息
app.get('/profile', (req, res) => {
const user = (req as any).user; // TokenPayload
res.json(user);
});NestJS
// app.module.ts
import { Module } from '@nestjs/common';
import { AuthClient, AUTH_CLIENT } from '@scorehub/auth-sdk';
@Module({
providers: [
{
provide: AUTH_CLIENT,
useFactory: () => new AuthClient({
serviceUrl: process.env.AUTH_SERVICE_URL,
clientId: process.env.AUTH_CLIENT_ID,
clientSecret: process.env.AUTH_CLIENT_SECRET,
publicKey: fs.readFileSync('./keys/public.pem'),
}),
},
],
exports: [AUTH_CLIENT],
})
export class AppModule {}
// controller
import { UseGuards } from '@nestjs/common';
import { AuthGuard } from '@scorehub/auth-sdk';
@UseGuards(AuthGuard)
@Controller('users')
export class UsersController {
@Get('me')
getProfile(@Request() req) {
return req.user; // TokenPayload
}
}Token 提取规则
中间件按以下优先级提取 Token:
| 优先级 | 来源 |
|--------|------|
| 1 | Authorization: Bearer <token> |
| 2 | x-access-token Header |
| 3 | access-token Header(仅 Koa)|
| 4 | access_token Header(仅 Koa)|
| 5 | ?token= Query 参数 |
错误处理
所有方法在失败时抛出带 authCode 属性的 Error:
try {
await authClient.loginByPassword(username, password);
} catch (err: any) {
console.log(err.message); // 错误描述
console.log(err.authCode); // 业务错误码,如 1001 (Unauthorized)
}