[英]NEXTJS: Invalid hook call
我在 nextjs 12 中使用新的中间件,并尝试使用 firebase 添加身份验证。
但是当我尝试使用钩子useAuthState
时,它给我一个错误消息:“无效的钩子调用。钩子只能在 function 组件的主体内部调用。这可能由于以下原因之一而发生:
除了在 pages 目录中创建 2 个组件外,我没有对应用程序进行任何更改
login.js 和 _middleware.js
这是我的 _middleware.js
import { NextResponse } from "next/server";
// import firebase from "firebase"
import { initializeApp } from "firebase/app";
import "firebase/auth";
import { getAuth } from "firebase/auth";
import { useAuthState } from "react-firebase-hooks/auth";
initializeApp({ ... });
const auth = getAuth();
export async function middleware(req) {
const [user] = useAuthState(auth);
const { pathname } = req.nextUrl;
if (!user) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}
这是我的 login.js
function login() {
const signInWithGoogle = () => {
console.log("Clicked!");
};
return (
<div>
<button onClick={signInWithGoogle}>Sign in with google</button>
</div>
);
}
export default login;
这是我的 package.json
{
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"firebase": "^9.5.0",
"next": "^12.0.4",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-firebase-hooks": "^4.0.1"
},
"devDependencies": {
"autoprefixer": "^10.2.6",
"postcss": "^8.3.5",
"tailwindcss": "^2.2.4"
}
}
根据反应文档
不要从常规的 JavaScript 函数调用 Hooks。 相反,您可以:
✅ 从 React function 组件调用 Hooks。
✅ 从自定义 Hooks 调用 Hooks(我们将在下一页了解它们)。
所以你在下面的常规 Javascript function 中使用钩子。
export async function middleware(req) { // this is regular JavaScript function
const [user] = useAuthState(auth); // here you're using hook
const { pathname } = req.nextUrl;
if (!user) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}
您的情况的解决方案可能是
import React, { useEffect } from "react";
import { NextResponse } from "next/server";
import { useAuthState } from "react-firebase-hooks/auth";
function login() {
const [user] = useAuthState(auth); // here you're using hook
const { pathname } = req.nextUrl;
useEffect(() => {
if (!user) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}, [user]);
const signInWithGoogle = () => {
console.log("Clicked!");
};
return (
<div>
<button onClick={signInWithGoogle}>Sign in with google</button>
</div>
);
}
export default login;
不要忘记在您的组件中导入所需的导入
在 nextjs文档之后:
next/server 模块为仅服务器助手提供了几个导出,例如Middleware
。
中间件使您能够使用代码而不是配置。 这为您在 Next.js 中提供了充分的灵活性,因为您可以在请求完成之前运行代码。 根据用户的传入请求,您可以通过重写、重定向、添加标头甚至流式传输 HTML 来修改响应。
中间件不是反应组件,不能使用钩子。
可悲的是中间件正在使用 V8 引擎https://v8.dev/和 Firebase package 不支持这个(浪费了太多时间)。 要对用户进行身份验证,您应该使用像jose
https://www.npmjs.com/package/jose这样的库自行解密
我设法使用 Node 18 和 node-fetch 来处理它。 您的代码在 typescript 上应如下所示:
import { NextResponse } from 'next/server'
import { jwtVerify, importX509, JWTPayload } from 'jose'
import fetch from 'node-fetch'
import type { NextRequest } from 'next/server'
interface TokenHeader {
alg: string
kid: string
typ: string
}
const authAlgorithm = 'RS256'
const appID = 'your-app-id-here'
const tokenISS = `https://securetoken.google.com/${appID}`
function verifyFirebasePayload(payload: JWTPayload) {
const currentDate = new Date()
if (
!payload ||
(payload.exp ?? 0) * 1000 < currentDate.getTime() ||
(payload.iat ?? currentDate.getTime()) * 1000 > currentDate.getTime() ||
payload.aud !== appID ||
payload.iss !== tokenISS ||
!payload.sub ||
!payload.user_id ||
payload.sub !== payload.user_id ||
(payload.auth_time as number) * 1000 > currentDate.getTime()
) {
throw Error('Token expired')
}
}
// function used to removed token cookies if it's invalid
function responseWithoutCookies(request: NextRequest) {
const response = NextResponse.redirect(new URL('/', request.url))
const { value, options } = request.cookies.getWithOptions('token')
if (value) {
response.cookies.set('token', value, options)
response.cookies.delete('token')
}
return response
}
async function getPayloadFromToken(token: string) {
const currentKeysx509: Record<string, string> = await (
await fetch(
'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'
)
).json() // this may need to be improved, too many queries?
const headerbase64 = token.split('.')[0] ?? ''
const headerConverted = JSON.parse(Buffer.from(headerbase64, 'base64').toString()) as TokenHeader
const matchKeyx509 = currentKeysx509[headerConverted.kid]
if (!matchKeyx509) {
throw Error('No match key')
}
const publicKey = await importX509(matchKeyx509, authAlgorithm)
const { payload } = await jwtVerify(token, publicKey, {
issuer: tokenISS,
audience: appID,
algorithms: [authAlgorithm]
})
return payload
}
async function middleware(request: NextRequest) {
const isHomepage = request.nextUrl.pathname === '/'
if (isHomepage || request.nextUrl.pathname.startsWith('/_next')) {
return NextResponse.next()
} // not necessary to process when the middleware is used internally
const token = request.cookies.get('token')
let isAuthenticated = false
if (!isHomepage && token) {
if (process.env.NEXT_PUBLIC_ENV !== 'prod') { // just for testing on `dev`
return NextResponse.next()
}
try {
const payload = await getPayloadFromToken(token)
verifyFirebasePayload(payload)
isAuthenticated = true
} catch (error) {
isAuthenticated = false
}
}
if (isAuthenticated) {
return NextResponse.next()
}
return responseWithoutCookies(request)
}
export default middleware
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.