@csrf-armor/ nextjs (中文(简体) )
Next.js 13+ 的 CSRF 保护软件: 中间软件、反应提供者和 useCsrf 钩. 与 App 路由器, Pages 路由器和服务器动作一起工作. 中间软件只使用Web API,因此运行于边缘跑道.
快开始
1. 安装
npm install @csrf-armor/nextjs
2. 保密
# .env.local — generate with: openssl rand -base64 32
CSRF_SECRET=your-generated-secret
3. 增加中间软件
创建 middleware.ts 在您的工程根中( 或在 src/) (中文(简体) ). 接下来的16岁,你可以命名 proxy.ts 和导出 proxy 相反;代码相同.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { createCsrfMiddleware } from '@csrf-armor/nextjs';
if (process.env.NODE_ENV === 'production' && !process.env.CSRF_SECRET) {
throw new Error('CSRF_SECRET is required in production');
}
const csrfProtect = createCsrfMiddleware({
secret: process.env.CSRF_SECRET,
cookie: { secure: process.env.NODE_ENV === 'production' },
});
export async function middleware(request: NextRequest) {
const result = await csrfProtect(request, NextResponse.next());
// The helper only reports the outcome; you must block failures yourself.
// On failure result.response is still the NextResponse.next() you passed in.
if (!result.success) {
console.warn('CSRF validation failed', {
method: request.method,
url: request.url,
reason: result.reason,
});
return NextResponse.json({ error: 'CSRF validation failed' }, { status: 403 });
}
return result.response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
4. 增加提供者
// App Router: app/layout.tsx
import { CsrfProvider } from '@csrf-armor/nextjs/client';
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<body>
<CsrfProvider>{children}</CsrfProvider>
</body>
</html>
);
}
// Pages Router: pages/_app.tsx
import type { AppProps } from 'next/app';
import { CsrfProvider } from '@csrf-armor/nextjs/client';
export default function App({ Component, pageProps }: AppProps) {
return (
<CsrfProvider>
<Component {...pageProps} />
</CsrfProvider>
);
}
5. 送来信
使用 csrfFetch 从钩子中取出; 它添加了符号头并取取回了响应中的旋转符号:
'use client'; // App Router only
import { useCsrf } from '@csrf-armor/nextjs/client';
export function ContactForm() {
const { csrfToken, csrfFetch } = useCsrf();
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const data = new FormData(e.currentTarget);
await csrfFetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: data.get('message') }),
});
}
return (
<form onSubmit={handleSubmit}>
<textarea name="message" required />
<button type="submit" disabled={!csrfToken}>Send</button>
</form>
);
}
API 路由不需要额外的代码: 在您的处理器运行之前, 中间软件已经验证了请求 .
服务器动作
包含一个隐藏的字段 。 行动必须宣布: FormData 参数,即使未使用,因为 Next.js 只发送已声明的参数和符号在窗体数据中运行:
'use client';
import { useCsrf } from '@csrf-armor/nextjs/client';
import { saveMessage } from './actions';
export function MessageForm() {
const { csrfToken } = useCsrf();
return (
<form action={saveMessage}>
<input type="hidden" name="csrf_token" value={csrfToken ?? ''} />
<input name="message" />
<button type="submit">Save</button>
</form>
);
}
// actions.ts
'use server';
export async function saveMessage(_formData: FormData) {
// Only runs if the middleware accepted the token
}
客户端 API
从 @csrf-armor/nextjs/client。 。 。 .
<CsrfProvider config?>
在React状态下保留了指使符,在路由更改和回向/前向导航上重新读取,并从响应头更新.
config 选项 | 默认 | 说明 |
|---|---|---|
cookieName | 'csrf-token' | Cookie来读信物的 匹配 cookie.name 在服务器上. |
headerName | 'x-csrf-token' | 寄信封的信头 匹配 token.headerName 在服务器上. |
initialToken | 无 | 切入开始,例如由服务器提供. |
refreshEndpoint | 当前路径 | URL 已获取以获取新符号 . |
useCsrf()
回返 { csrfToken, csrfFetch, updateToken }数字 :
csrfToken: string | null: 当前符号csrfFetch(input, init?)数字 :fetch发送符号头并存储任何来自响应的新符号updateToken(): 从饼干上重新读取符号
外部使用时投掷 CsrfProvider。 。 。 .
每个路线的不同策略
每个策略创建一个保护者,并选择每个请求:
const apiCsrf = createCsrfMiddleware({ strategy: 'signed-token', secret: process.env.CSRF_SECRET });
const webCsrf = createCsrfMiddleware({ secret: process.env.CSRF_SECRET });
export async function middleware(request: NextRequest) {
const protect = request.nextUrl.pathname.startsWith('/api/') ? apiCsrf : webCsrf;
const result = await protect(request, NextResponse.next());
return result.success
? result.response
: NextResponse.json({ error: 'CSRF validation failed' }, { status: 403 });
}
给每个守护者一个不同的 cookie.name 如果两者都可以适用于同一浏览器.
配置
createCsrfMiddleware() 接受共享 CsrfConfig所有选项都是可选的; secret 在生产中需要.
一个完整的示例应用程序,包括用于测试每个策略的攻击者页面,生活在 examples/nextjs-app。 。 。 .
许可证
麻省理工学院 萨缪尔斯