[英]NEXTJS: Invalid hook call
I'm using the new middleware in nextjs 12 and am trying to add authentication using firebase.我在 nextjs 12 中使用新的中间件,并尝试使用 firebase 添加身份验证。
But when i try to use the hook useAuthState
it's giving me an error saying: "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:但是当我尝试使用钩子useAuthState
时,它给我一个错误消息:“无效的钩子调用。钩子只能在 function 组件的主体内部调用。这可能由于以下原因之一而发生:
I have not made any changes in the app other than creating 2 components both in pages directory除了在 pages 目录中创建 2 个组件外,我没有对应用程序进行任何更改
login.js and _middleware.js login.js 和 _middleware.js
here is my _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();
}
Here is my login.js这是我的 login.js
function login() {
const signInWithGoogle = () => {
console.log("Clicked!");
};
return (
<div>
<button onClick={signInWithGoogle}>Sign in with google</button>
</div>
);
}
export default login;
And here's my package.json这是我的 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"
}
}
According to React docs根据反应文档
Don't call Hooks from regular JavaScript functions.不要从常规的 JavaScript 函数调用 Hooks。 Instead, you can:相反,您可以:
✅ Call Hooks from React function components. ✅ 从 React function 组件调用 Hooks。
✅ Call Hooks from custom Hooks (we'll learn about them on the next page). ✅ 从自定义 Hooks 调用 Hooks(我们将在下一页了解它们)。
So you're using hooks inside regular Javascript function below.所以你在下面的常规 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();
}
Solution for you case might be您的情况的解决方案可能是
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;
Don't forget to import requried imports in your component不要忘记在您的组件中导入所需的导入
Following nextjs documentation :在 nextjs文档之后:
The next/server module provides several exports for server-only helpers, such as Middleware
. next/server 模块为仅服务器助手提供了几个导出,例如Middleware
。
Middleware enables you to use code over configuration.中间件使您能够使用代码而不是配置。 This gives you full flexibility in Next.js, because you can run code before a request is completed.这为您在 Next.js 中提供了充分的灵活性,因为您可以在请求完成之前运行代码。 Based on the user's incoming request, you can modify the response by rewriting, redirecting, adding headers, or even streaming HTML.根据用户的传入请求,您可以通过重写、重定向、添加标头甚至流式传输 HTML 来修改响应。
The middleware is not a react component and cannot use hooks.中间件不是反应组件,不能使用钩子。
Sadly Middleware is using V8 engine https://v8.dev/ and Firebase package doesn't support this (wasted too many hours).可悲的是中间件正在使用 V8 引擎https://v8.dev/和 Firebase package 不支持这个(浪费了太多时间)。 To authenticate a user you should decrypt it yourself with a library like jose
https://www.npmjs.com/package/jose要对用户进行身份验证,您应该使用像jose
https://www.npmjs.com/package/jose这样的库自行解密
I managed to work with it using Node 18 and node-fetch.我设法使用 Node 18 和 node-fetch 来处理它。 Your code should look like this on typescript:您的代码在 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.