简体   繁体   English

NEXTJS:无效的钩子调用

[英]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 组件的主体内部调用。这可能由于以下原因之一而发生:

  1. You might have mismatching versions of React and the renderer (such as React DOM)您可能有不匹配的 React 版本和渲染器(例如 React DOM)
  2. You might be breaking the Rules of Hooks你可能违反了 Hooks 规则
  3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.您可能在同一个应用程序中有多个 React 副本 请参阅https://reactjs.org/link/invalid-hook-call以获取有关如何调试和修复此问题的提示。 null"无效的”

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文档之后:

next/server下一个/服务器

The next/server module provides several exports for server-only helpers, such as Middleware . next/server 模块为仅服务器助手提供了几个导出,例如Middleware

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.

相关问题 无效的钩子调用(redux-saga) - Invalid hook call(redux-saga) 我在搜索 function 时收到无效挂钩调用错误 - I receive an invalid hook call error in the search function 尝试使用 React + Redux 工具包查询在 Firestore 上创建文档时挂钩调用无效 - Invalid Hook Call when trying to create document on Firestore with React + Redux Toolkit Query 如何在特定的 NextJS 中使用 React Hook? (火力地堡身份验证) - How to use react hook in specific NextJS? (Firebase Authentication) React.js - 有条件地调用 useDocument 挂钩 - React.js - call useDocument hook conditionally 使用 NextJS 和 Amplify 的 AWS Cognito 登录 - 在前端或后端调用端点? - AWS Cognito Signin with NextJS and Amplify - call endpoint on front or backend? 我怎样才能从 Nextjs 调用云秘密 - How can I call cloud secret from Nextjs 如何在每次渲染时使用不同的查询调用自定义钩子 useFetch? - How to call custom hook useFetch with different query per render? nextjs ISR 与云端 - nextjs ISR with cloudfront 在 AWS Amplify 上使用@sentry/nextjs 构建 NextJS 时出错 - Error building NextJS with @sentry/nextjs on AWS Amplify
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM