@turinhub/tale-next-auth
v0.2.1
Published
基于 @turinhub/tale-js-sdk 的 Next.js 登录、注册与会话集成组件包
Maintainers
Readme
@turinhub/tale-next-auth
Tale 登录能力的 Next.js 集成包。底层认证、短信、注册和 Token 校验由 @turinhub/tale-js-sdk 提供;本包负责 React 组件、Hooks、App Router server helpers 和 httpOnly cookie session。
安装
pnpm add @turinhub/tale-next-auth @turinhub/tale-js-sdkPeer dependencies:
{
"@turinhub/tale-js-sdk": ">=2.3.0 <3.0.0",
"next": ">=13.0.0 <17.0.0",
"react": ">=18.0.0 <20.0.0",
"react-dom": ">=18.0.0 <20.0.0"
}环境变量
TALE_BASE_URL=https://api.example.com
TALE_APP_KEY=your_app_key
TALE_APP_SECRET=your_app_secretTALE_APP_SECRET 只能在服务端使用,不要放进客户端组件或 NEXT_PUBLIC_* 环境变量。
App Router 示例
登录页
"use client";
import {
AuthProvider,
AuthContainer,
LoginForm,
} from "@turinhub/tale-next-auth/client";
import "@turinhub/tale-next-auth/styles.css";
export default function LoginPage() {
return (
<AuthProvider storage="cookie">
<AuthContainer>
<LoginForm redirectPath="/dashboard" showPhoneLogin={false} />
</AuthContainer>
</AuthProvider>
);
}styles.css 只提供组件类和 .tale-auth-theme 作用域内的默认主题变量,不包含 Tailwind preflight,也不会覆盖宿主项目的 :root 或 body 样式。
自定义 CSS
宿主项目可以通过 CSS 变量和 className 自定义样式:
.brand-auth {
--primary: 217 91% 60%;
--primary-foreground: 0 0% 100%;
--ring: 217 91% 60%;
--radius: 0.5rem;
}<AuthContainer className="brand-auth">
<LoginForm className="shadow-none" redirectPath="/dashboard" />
</AuthContainer>styles.css 是默认样式入口;如需完全接管样式,可以不引入它,但宿主项目需要自行生成组件使用到的 Tailwind utility class,并提供 --background、--foreground、--primary、--border、--input、--ring 等主题变量。完整说明见 docs/theming.md。
登录 Route Handler
import { NextResponse } from "next/server";
import { createTaleAuthServer } from "@turinhub/tale-next-auth/server";
const auth = createTaleAuthServer({
baseUrl: process.env.TALE_BASE_URL!,
appKey: process.env.TALE_APP_KEY!,
appSecret: process.env.TALE_APP_SECRET!,
});
export async function POST(request: Request) {
const credentials = await request.json();
const result = await auth.loginWithCredentials(credentials);
return NextResponse.json(result);
}Session Route Handler
import { NextResponse } from "next/server";
import { createTaleAuthServer } from "@turinhub/tale-next-auth/server";
const auth = createTaleAuthServer({
baseUrl: process.env.TALE_BASE_URL!,
appKey: process.env.TALE_APP_KEY!,
appSecret: process.env.TALE_APP_SECRET!,
});
export async function GET() {
return NextResponse.json(await auth.validateSession());
}Logout Route Handler
import { NextResponse } from "next/server";
import { createTaleAuthServer } from "@turinhub/tale-next-auth/server";
const auth = createTaleAuthServer({
baseUrl: process.env.TALE_BASE_URL!,
appKey: process.env.TALE_APP_KEY!,
appSecret: process.env.TALE_APP_SECRET!,
});
export async function POST() {
await auth.clearSession();
return NextResponse.json({ ok: true });
}客户端状态
"use client";
import { AuthProvider, useTaleAuth } from "@turinhub/tale-next-auth/client";
export function Providers({ children }: { children: React.ReactNode }) {
return <AuthProvider storage="cookie">{children}</AuthProvider>;
}
export function AccountMenu() {
const { user, isAuthenticated, logout } = useTaleAuth();
if (!isAuthenticated) return null;
return (
<button type="button" onClick={() => void logout()}>
退出 {user?.username}
</button>
);
}AuthProvider 默认使用 httpOnly cookie session,并调用:
POST /api/auth/loginPOST /api/auth/logoutGET /api/auth/session
cookie session 恢复时不会把用户 token 返回给浏览器,token 会是 null。需要 token 的业务请求应放在 Route Handler 或 Server Action 中完成。
如需轻量 demo 或兼容旧项目,可使用 storage="localStorage",但生产环境推荐 cookie。
Public APIs
import {
AuthProvider,
LoginForm,
PasswordResetForm,
RegisterForm,
useTaleAuth,
} from "@turinhub/tale-next-auth/client";
import { createTaleAuthServer } from "@turinhub/tale-next-auth/server";
import "@turinhub/tale-next-auth/styles.css";账号密码登录可以使用默认 endpoint,也支持注入业务 handler。短信登录和注册需要注入服务端 API handler,避免在浏览器里调用需要 app token 的 SDK 能力:
<LoginForm
onLogin={(credentials) => fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(credentials),
}).then((res) => res.json())}
onSendSmsCode={(phone) => fetch("/api/auth/send-sms", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone }),
}).then((res) => res.json())}
onVerifySmsCode={(request) => fetch("/api/auth/verify-sms", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
}).then((res) => res.json())}
/>找回密码使用独立表单,并注入服务端短信和重置密码 handler:
<PasswordResetForm
redirectPath="/login"
onSendSmsCode={(phone) => fetch("/api/auth/send-password-sms", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone }),
}).then((res) => res.json())}
onResetPassword={(request) => fetch("/api/auth/reset-password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
}).then((res) => res.json())}
/>SMS、注册与找回密码 Route Handlers
app/api/auth/send-sms/route.ts:
import { NextResponse } from "next/server";
import { createTaleAuthServer } from "@turinhub/tale-next-auth/server";
const auth = createTaleAuthServer({
baseUrl: process.env.TALE_BASE_URL!,
appKey: process.env.TALE_APP_KEY!,
appSecret: process.env.TALE_APP_SECRET!,
});
export async function POST(request: Request) {
const { phone } = await request.json();
return NextResponse.json(await auth.sendSmsCode(phone));
}app/api/auth/verify-sms/route.ts:
import { NextResponse } from "next/server";
import { createTaleAuthServer } from "@turinhub/tale-next-auth/server";
const auth = createTaleAuthServer({
baseUrl: process.env.TALE_BASE_URL!,
appKey: process.env.TALE_APP_KEY!,
appSecret: process.env.TALE_APP_SECRET!,
});
export async function POST(request: Request) {
return NextResponse.json(await auth.verifySmsCode(await request.json()));
}app/api/auth/register/route.ts:
import { NextResponse } from "next/server";
import { createTaleAuthServer } from "@turinhub/tale-next-auth/server";
const auth = createTaleAuthServer({
baseUrl: process.env.TALE_BASE_URL!,
appKey: process.env.TALE_APP_KEY!,
appSecret: process.env.TALE_APP_SECRET!,
});
export async function POST(request: Request) {
return NextResponse.json(await auth.registerWithSms(await request.json()));
}app/api/auth/send-password-sms/route.ts:
import { NextResponse } from "next/server";
import { createTaleAuthServer } from "@turinhub/tale-next-auth/server";
const auth = createTaleAuthServer({
baseUrl: process.env.TALE_BASE_URL!,
appKey: process.env.TALE_APP_KEY!,
appSecret: process.env.TALE_APP_SECRET!,
});
export async function POST(request: Request) {
const { phone } = await request.json();
return NextResponse.json(await auth.sendPasswordChangeSmsCode(phone));
}app/api/auth/reset-password/route.ts:
import { NextResponse } from "next/server";
import { createTaleAuthServer } from "@turinhub/tale-next-auth/server";
const auth = createTaleAuthServer({
baseUrl: process.env.TALE_BASE_URL!,
appKey: process.env.TALE_APP_KEY!,
appSecret: process.env.TALE_APP_SECRET!,
});
export async function POST(request: Request) {
return NextResponse.json(await auth.resetPasswordWithSms(await request.json()));
}开发
pnpm run type-check
pnpm run build