@aws- blocks/ bb-auth- basic (英语)
使用 JWT 会话的简单用户名/密码认证,密码政策,以及可选代码确认的签名和密码重置.
何时使用 : 原型,内部工具,MVP,或不需要OAuth/OIDC复杂性的应用.
不使用时 : 用于社交签名( Google, GitHub), 使用 AuthOIDC。对于外交部,用户组,或自定义用户属性,使用 AuthCognito。 。 。 .
设计与模拟等价细节 : 设计
快速启动
import { Scope, ApiNamespace } from '@aws-blocks/core';
import { AuthBasic } from '@aws-blocks/bb-auth-basic';
const scope = new Scope('my-app');
const auth = new AuthBasic(scope, 'auth', {
sessionDuration: 86400,
passwordPolicy: { minLength: 8, requireDigits: true },
// Optional: enable code-confirmed signup and password reset
codeDelivery: async (username, code) => {
await sendEmail(username, `Your verification code: ${code}`);
},
});
export const api = new ApiNamespace(scope, 'api', (context) => ({
async getProfile() {
const user = await auth.requireAuth(context);
return { username: user.username, createdAt: user.createdAt };
},
}));
// Export the state machine API for the Authenticator component
export const authApi = auth.createApi();
客户端命名空间取自 导出名称 您选择这里( 因此 )
import { authApi } from 'aws-blocks'匹配); 该'auth'内部标签createApi()是内部的,不影响线名空间.
前端: 提供登录 UI 并打开应用程序
导出 authApi 提供后端,但 它不会渲染登录屏幕 - 您的 SPA 仍然不显示, 直到您挂载一个认证组件 。 如果您停在后端, 一个认证的应用程序打开了一个空白/登陆页面, 而每个登录的路由都是无法到达的 。 挂载已运出 Authenticator UI在你的SPA入口 并打开其后面的应用程序外壳:
// src/index.ts — the SPA entrypoint
import { api, authApi } from 'aws-blocks';
import { AccountMenuBar, AuthenticatedContent } from '@aws-blocks/blocks/ui';
// A Sign In / account control that opens the Authenticator (sign-up / sign-in /
// confirm-code / reset) when clicked. No testids to wire by hand — the shipped
// components already carry them (see auth-common/CUSTOMIZING-AUTH-UI.md).
document.getElementById('menu-bar')!.appendChild(AccountMenuBar(authApi));
// Gate the app shell: the render callback runs only when a session exists;
// the fallback shows while signed out.
const signInMessage = document.createElement('p');
signInMessage.textContent = 'Sign in to get started.';
document.getElementById('app')!.appendChild(
AuthenticatedContent(authApi, (user) => {
const el = document.createElement('div');
el.textContent = `Welcome, ${user.username}`;
// ...build the authenticated app here, calling your `api` methods...
return el;
}, signInMessage),
);
AccountMenuBar(authApi)使弹出Authenticator。要将窗体嵌入内线,请使用Authenticator(authApi)直接说.AuthenticatedContent(authApi, render, fallback)订阅会话并在render(user)财务报告和审定财务报表fallback在注册/退出时——这是贝壳的大门.- 都来自
@aws-blocks/blocks/ui(从@aws-blocks/auth-common/ui) (中文(简体) ). - 登入/签入的球场和扣子船的稳定测试仪 一个e2e套件可以直接瞄准——
authenticator-username, (中文(简体) ),authenticator-password, (中文(简体) ),authenticator-code,以及authenticator-action-signUp编号 :authenticator-action-signIn(一个)authenticator-<field>每个输入,一个authenticator-action-<action>(每个行动)。 完全选择者合同是在auth-common/CUSTOMIZING-AUTH-UI.md; 不要将组件叉入添加自己的组件.
见脚手架 default 模板 src/index.ts 为了一个完整的工作应用程序 在这个接合.
API 密码
const auth = new AuthBasic(scope, id, options?)
服务器系统方法
| 方法 | 回返 | 说明 |
|---|---|---|
requireAuth(context) | Promise<AuthBasicUser> | 需要认证 。 掷出401- SessionRevokedException 如果会议被撤销(签署),否则 SessionExpiredException 如果缺席/期满. |
checkAuth(context) | Promise<boolean> | 检查是否注册. |
getCurrentUser(context) | Promise<AuthBasicUser | null> | 获取当前用户或无效 . |
signUp(username, password) | Promise<void> | 注册新用户 。 若为 codeDelivery 设置, 用户在代码核实前未经确认 . |
confirmSignUp(username, code) | Promise<void> | 以验证码确认注册 。 只有当 codeDelivery 已经设定. |
signIn(username, password, context) | Promise<AuthBasicUser> | 认证并设定会话 cookie 。 拒绝未经证实的用户。 何时 lockout 启用,丢出 AccountLockedException (429)在多次尝试失败后,账户被锁定. |
signOut(context) | Promise<void> | 撤销所呈现的会话服务器侧( 拒绝重放的 cookie) 并清除会话 cookie 。 本地 cookie 总是被清除; 如果服务器端的撤销写入失败, 它会丢弃 SignOutIncompleteException (503年),使召相能再试. |
resetPassword(username) | Promise<void> | 发送密码重置代码 。 只有当 codeDelivery 已经设定. |
confirmResetPassword(username, code, newPassword) | Promise<void> | 使用校验码完成密码重置 . |
createApi() | ApiNamespace | 为认证器组件创建状态机器 API . |
动作输入形状 : createApi() 返回国家机器 API. setAuthState 使用单一 FLAT 歧视性输入 { action, ...fields }从不筑巢 { action, data: {...} }实例: { action: 'signIn', username, password }, (中文(简体) ), { action: 'signUp', username, password }, (中文(简体) ), { action: 'confirmSignUp', username, code, password }, (中文(简体) ), { action: 'resetPassword', username }, (中文(简体) ), { action: 'confirmResetPassword', username, code, newPassword }见 AuthActionInput 类型在 @aws-blocks/auth-common。 。 。 .
buildApi() (折旧)
折旧-使用
createApi()。 。 。 . 为后向相容性保留. 与createApi()(它揭露getAuthState编号 :setAuthState国家机器,buildApi()返回一个ApiNamespace带有平坦的RPC表面. 此处记录了这样现有的呼叫者可以找到形状. 请求context被命名空间工厂捕获,所以曝光的方法没有context参数 :
| 方法 | 签署 | 回返 |
|---|---|---|
signUp(username, password) | (username: string, password: string) => Promise<{ username: string }> | { username }。当 codeDelivery 实值 没有 已配置 . |
signIn(username, password) | (username: string, password: string) => Promise<{ username: string }> | { username }设置会话饼干. |
signOut() | () => Promise<{ success: true }> | { success: true }。撤消所呈现的会话服务器侧并清除会话 cookie. |
getCurrentUser() | () => Promise<AuthBasicUser | null> | 注册用户,或 null。 。 。 . |
选项
| 选项 | 类型 | 默认 | 说明 |
|---|---|---|---|
sessionDuration | number | 86400 (24小时) | 会期数秒. |
passwordPolicy | PasswordPolicy | {} | 密码要求. |
codeDelivery | (username, code) => Promise<void> | —— 说 | 代码发送回调。 启用确认的注册和密码重置 . |
logger | ChildLogger | —— 说 | 内部操作的可选日志。 省略时,会在错误级别创建默认 Logger . |
crossDomain | boolean | false | 设定为 true 当前端和API处于不同的可注册域上时(例如:前端在Vercel上,API在AWS上). 切换会话 cookie 到 SameSite=None; Secure; Partitioned。 。 。 . |
lockout | LockoutPolicy | ——(残疾) | 签到冻结/账户关闭。 关闭禁用( 默认) 。 过 {} 以默认方式启用,或覆盖 maxAttempts (5), (中文), windowSeconds (900个), lockoutSeconds (900) (英语). 见 签名催促。 。 。 . |
密码策略
| 外地 | 类型 | 默认 | 说明 |
|---|---|---|---|
minLength | number | 8 | 最小密码长度 . |
requireUppercase | boolean | false | 需要大写字母 . |
requireLowercase | boolean | false | 需要小写字母 . |
requireDigits | boolean | false | 需要数字. |
requireSpecialChars | boolean | false | 需要特殊性格 . |
代码传送
何时 codeDelivery 规定如下:
- 注册 创建未确认状态的用户。 代码通过回调发送. 用户必须打电话
confirmSignUp在他们签字之前. - 密码重置 作为国家机器行动可以使用。 代码通过回调发送. 用户输入代码+新密码来完成重置.
- 校验代码为6位数字符串,有效期为10分钟.
- 代码在存储之前是 HMAC- hashed —— 从未以纯文本存储.
- 每个代码最多允许5个失败的猜想(签名确认和密码重置相同);在第5个错误猜想后代码无效,用户必须要求一个新的. 6位数的代码在其10分钟的窗口内可以野蛮地执行,而不使用它 关闭。 。 。 .
何时 codeDelivery 实值 没有 条件是:
- 即时报名(无确认步骤).
- 密码重置不可用( 动作不出现在状态机器中) .
处理错误
import { isBlocksError } from '@aws-blocks/core';
import { AuthBasicErrors } from '@aws-blocks/bb-auth-basic';
try {
await auth.signIn('alice', 'wrong', context);
} catch (e) {
if (isBlocksError(e, AuthBasicErrors.InvalidCredentials)) {
// bad username or password
}
}
| 错误常数 | 名称 | 何时 |
|---|---|---|
AuthBasicErrors.InvalidCredentials | InvalidCredentialsException | 错误的用户名或密码, 或未经证实的用户 |
AuthBasicErrors.UserAlreadyExists | UserAlreadyExistsException | 注册时复制用户名 |
AuthBasicErrors.InvalidPassword | InvalidPasswordException | 密码不符合策略 |
AuthBasicErrors.SessionExpired | SessionExpiredException | requireAuth 没有/过期的会话 |
AuthBasicErrors.SessionRevoked | SessionRevokedException | requireAuth 带有在签出时被吊销的标志(尚未过期,但已失效) |
AuthBasicErrors.InvalidCode | InvalidCodeException | 错误或过期的验证代码( 签名或重设) |
AuthBasicErrors.SignOutIncomplete | SignOutIncompleteException | signOut 清除本地 cookie, 但服务器端的撤销写入失败( 例如 DynamoDB 节流器); 状态 503 —— 重试以撤销该令牌 |
AuthBasicErrors.AccountLocked | AccountLockedException | signIn 拒绝,因为账户在多次失败尝试后被锁定(只有在 lockout 启用);状态 429 — 在关闭窗口后再次尝试 |
分会 setAuthState 客户端路径
isBlocksError 工作在一个 抛出 错误。 建议的客户端路径( E)createApi() → setAuthState())不扔出——它返回一个 AuthState 谁 errorName 带有相同的结构名称。 使用 hasAuthError 到返回状态上的分支, 例如返回到一个全新的用户的注册 :
import { hasAuthError } from '@aws-blocks/core';
import { AuthBasicErrors } from '@aws-blocks/bb-auth-basic';
let next = await authApi.setAuthState({ action: 'signIn', username, password });
if (hasAuthError(next, AuthBasicErrors.InvalidCredentials)) {
// unknown or wrong-credential user → offer sign-up instead
next = await authApi.setAuthState({ action: 'signUp', username, password });
}
拇指规则: 抛出路径 isBlocksError; 返回时 AuthState → hasAuthError。 。 。 . 永远不要和人类相配 error 绳子.
UI 组件
此包不包括UI组件. 使用来自 @aws-blocks/auth-common/ui数字 :
import { Authenticator } from '@aws-blocks/auth-common/ui';
import { authApi } from 'aws-blocks';
document.body.appendChild(Authenticator(authApi));
见 auth-common 读取 用于完整的UI组件文档,包括 AuthenticatedContent, (中文(简体) ), onAuthChange,以及 broadcastAuthChange。 。 。 .
认证基本用户
扩展常见 AuthUser 改为:
| 外地 | 类型 | 说明 |
|---|---|---|
userId | string | 与用户名相同 |
username | string | 用户名 |
createdAt | string | ISO 8601 创建时间戳 |
再出口类型
这些类型来自: @aws-blocks/auth-common 由此软件包再导出,以便直接从 @aws-blocks/bb-auth-basic 不添加对 auth-common数字 :
import type {
BlocksAuth, AuthUser, AuthState, AuthActionInput, AuthAction, AuthField,
} from '@aws-blocks/bb-auth-basic';
| 类型 | 仁爱 | 形状/目的 |
|---|---|---|
BlocksAuth | 接口 | 普通服务器侧认证合同 AuthBasic 执行: requireAuth(context): Promise<AuthUser>, (中文(简体) ), checkAuth(context): Promise<boolean>, (中文(简体) ), getCurrentUser(context): Promise<AuthUser | null>。 。 。 . |
AuthUser | 接口 | 基本用户形状 : { userId: string; username: string }。 。 。 . AuthBasicUser 扩展( 添加) createdAt) (中文(简体) ). |
AuthState | 接口 | 国家返回 getAuthState() 编号 : setAuthState()数字 : { state, user?, actions, error?, errorName?, retriable? }时 state 实值 'signedOut' | 'signedIn' | 'confirmingSignUp' | 'confirmingSignIn' | 'confirmingMfa' | 'confirmingPasswordReset'。 。 。 . |
AuthAction | 接口 | 一个国家提出的一项行动: { name, label, fields: AuthField[], url?, method?: 'GET' | 'POST', capability?: 'webauthn-get' | 'webauthn-create' }。 。 。 . |
AuthField | 接口 | 用于渲染: { name, label, type: 'text' | 'password' | 'email' | 'tel' | 'number' | 'hidden', required: boolean, defaultValue? }。 。 。 . |
AuthActionInput | 类型 | 歧视的结合(按 action转至 setAuthState。变体 Auth Basic hands 是 signIn, (中文(简体) ), signUp, (中文(简体) ), confirmSignUp, (中文(简体) ), signOut, (中文(简体) ), resetPassword,以及 confirmResetPassword (见下面的行动投入说明) API 密码) (中文(简体) ). |
饼干和会议
本届会议是 HttpOnly 饼干持有一个签名的JWT。 默认饼干是 SameSite=Lax (单位:千美元) Secure 这对同源的应用程序和本地的dev代理来说是正确的.
当你通过Hosting块服务前端时, CloudFront 将静态站点和API 放在后面 一个来源 (API是同一分布的路径),所以所部署的app是同源的,并且 SameSite=Lax 默认工作, 没有任何配置 —— 离开 crossDomain 默认时 false。 。 。 .
设定 crossDomain: true 只在前端和API从 不同的可注册域 在生产中(例如Vercel上的前端,AWS上的API). 那把饼干切到 SameSite=None; Secure; Partitioned 因此,它经受住了跨地点的要求:
const auth = new AuthBasic(app, 'auth', { crossDomain: true });
在 plain-HTTP 本地主机上 BB 滴入 Secure 联 合 国 Lax 默认和下降 Partitioned 用于跨域食谱(CHIPS需要HTTPS).
撤销:读取费用和一致性
服务器端的退出( 见 signOut) 添加 一个迪纳摩DB GetItem 每个经认证的请求 —— 说 checkAuth, (中文(简体) ), getCurrentUser, (中文(简体) ), requireAuth,则 getAuthState 调用在页面加载上运行的每个检查符号 jti 反对撤销名单。 与纯无国籍的JWT验证相比, 撤销前签发的托肯( 无) jti) 跳过读取,在读取中直接过期 sessionDuration。 。 。 .
撤销读取是最终一致的 GetItem (无) ConsistentRead因此,撤销是 近即时而非瞬间: 对于后面的子秒窗口 signOut在撤销标记发布之前, 签出的信物停止在宣传窗口内到处工作,总是在自己的时间过期 exp 随便.
签名催促
野蛮武力和威信攻击 signIn 带有密码的猜测。 启用 lockout 在连续多次失败后锁定账户的政策—— 与Cognito管理的关闭点对等 AuthCognito数字 :
// Lock an account for 15 minutes after 5 consecutive failed sign-ins (defaults)
const auth = new AuthBasic(app, 'auth', { lockout: {} });
// Stricter: 3 attempts, 30-minute lock
const auth = new AuthBasic(app, 'auth', {
lockout: { maxAttempts: 3, lockoutSeconds: 1800 },
});
| 外地 | 默认 | 含义 |
|---|---|---|
maxAttempts | 5 | 连续失效的签入 绊倒了锁. |
windowSeconds | 900 (15分钟) | 固定窗口,从第一个失败中被测量,其中的失败被计数——一旦它冲出下一个失败,就会重排运行中的计数,如此缓慢,被间隔的猜测不会累积. |
lockoutSeconds | 900 (15分钟) | 账户被绊倒后会锁多久 锁自通(无人工解锁). |
锁门的时候, signIn 掷出 AuthBasicErrors.AccountLocked (单位:千美元)AccountLockedException页:1 在此之前 密码被检查,所以一个被锁定的账户会流出被攻击的bcrypt成本,并且决定中没有密码依赖的时间信号. 成功签入可以清出柜台. 计数是 每个用户名 (证书-上接目标);它不会被IP锁定,所以共享NAT不是假阳性风险.
喉咙是 选入 没有 lockout 选项, 登录是无限的, 行为与以前完全一样。 计数器与验证码和吊销标记一样,生活在TTL后置的同一家商店中,因此它们自取而出,从不生长出无约束.
计数器是一个最优的读取-修改-写出,而不是原子增量,因此同一用户名(在很多Lambda实例中)的高度并发的故障可以失去增量,使锁出行稍晚于 maxAttempts 建议。 锁定窗口是从数数到达时开始测量的 maxAttempts所以锁总能撑住 lockoutSeconds 一次绊倒.
地方发展: 模拟 KVStore 是模拟, 所以当 dev 服务器重新启动时, 锁定计数器( 如 AuthBasic state) 重置 .