且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何在 Next.js 中实现身份验证

更新时间:2022-12-06 21:57:26

由于我们正在隔离,我有足够的时间来回答这个问题.这将是一个很长的答案.

Since we are on quarantine I have enough time to answer this question. It will be a long answer.

Next.js 使用 App 组件来初始化页面._app page 负责渲染我们的页面.我们在 _app.js 上对用户进行身份验证,因为我们从 getInitialProps 返回的任何内容都可以被所有其他页面访问.我们在这里对用户进行身份验证,身份验证决策将传递到页面,从页面到标题,因此每个页面都可以决定用户是否通过身份验证.(请注意,它可以使用 redux 来完成而不用 prop 钻孔,但它会使答案更复杂)

Next.js uses the App component to initialize the pages. _app page is responsible for rendering our pages. We authenticate users on _app.js because anything that we return from getInitialProps can be accessed by all of the other pages. We authenticate user here, authentication decision will be passed to pages, from pages to header, so each page can decide if the user is authenticated or not. (Note that it could be done with redux without prop drilling but it would make the answer more complex)

  static async getInitialProps({ Component, router, ctx }) {
    let pageProps = {};
    const user = process.browser
      ? await auth0.clientAuth()
      : await auth0.serverAuth(ctx.req); // I explain down below

    //this will be sent to all the components
    const auth = { user, isAuthenticated: !!user };
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
    }

    return { pageProps, auth };
  }

  render() {
    const { Component, pageProps, auth } = this.props;
    return <Component {...pageProps} auth={auth} />;
  }
}

如果我们在浏览器上并且需要检查用户是否经过身份验证,我们只需从浏览器中检索 cookie,这很容易.但是我们总是要验证令牌.它与浏览器和服务器使用的过程相同.我将在下面解释.但是如果我们在服务器上.我们无法访问浏览器中的 cookie.但是我们可以从req"中读取.对象,因为 cookie 附加到 req.header.cookie. 这是我们访问服务器上 cookie 的方式.

If we are on the browser and need to check if a user is authenticated, we just retrieve the cookie from the browser, which is easy. But we always have to verify the token. It is the same process used by browser and server. I will explain down below. But if we are on the server. we have no access to the cookies in the browser. But we can read from the "req" object because cookies are attached to the req.header.cookie. this is how we access to cookies on the server.

async serverAuth(req) {
    // console.log(req.headers.cookie) to check
    if (req.headers.cookie) {
      const token = getCookieFromReq(req, "jwt");
      const verifiedToken = await this.verifyToken(token);
      return verifiedToken;
    }
    return undefined;
  }

这里是 getCookieFromReq().请记住,我们必须考虑功能性.

here is getCookieFromReq(). remember we have to think functional.

const getCookieFromReq = (req, cookieKey) => {
  const cookie = req.headers.cookie
    .split(";")
    .find((c) => c.trim().startsWith(`${cookieKey}=`));

  if (!cookie) return undefined;
  return cookie.split("=")[1];
};

一旦我们得到cookie,我们必须对其进行解码,提取过期时间以查看它是否有效.这部分很简单.我们必须检查的另一件事是 jwt 的签名是否有效.对称或非对称算法用于签署 jwt.您必须使用私钥来验证对称算法的签名.RS256 是 API 的默认非对称算法.使用 RS256 的服务器为您提供了一个链接,以获取 jwt 以使用密钥来验证签名.你可以使用 [jwks-rsa][1] 也可以自己做.您必须创建一个证书,然后验证令牌是否有效.

Once we get the cookie, we have to decode it, extract the expiration time to see if it is valid or not. this part is easy. Another thing we have to check is if the signature of the jwt is valid. Symmetric or asymmetric algorithms are used to sign the jwt. You have to use private keys to validate the signature of symmetric algorithms. RS256 is the default asymmetric algorithms for APIs. Servers that use RS256, provide you with a link to get jwt to use the keys to validate the signature. You can either use [jwks-rsa][1] or you can do on your own. You have to create a certificate and then verify if the token is valid.

假设我们的用户现在通过了身份验证.你说,而且我见过很多人,在受保护路由的 getInitialProps 中,他们只检查 cookie/localStorage 中的存在令牌,".我们使用受保护的路由仅向授权用户提供访问权限.为了访问这些路由,用户必须显示他们的 jwt 令牌,并且 express.js 使用中间件来检查用户的令牌是否有效.由于您已经看过很多示例,因此我将跳过这一部分.

Assume that our user authenticated now. You said, "And I've seen a lot of people, in getInitialProps of the protected route, they only check for existence token in cookie / localStorage,". We use protected routes to give access only to the authorized users. In order to access those routes, users have to show their jwt tokens and express.js uses middlewares to check if the user's token is valid. Since you have seen a lot of examples, I will skip this part.

那么如果令牌被撤销或列入黑名单怎么办,因为他们没有将令牌发送到服务器,他们如何处理呢?还是我必须在每个客户端页面更改时将令牌发送到服务器?"

"then what if the token is being revoked or blacklisted, how do they handle it because they did not send the token to the server? Or do I have to send the token to a server in every client-side page changing?"

通过验证令牌过程,我们 100% 确定令牌是否有效.当客户端要求服务器访问一些秘密数据时,客户端必须将令牌发送到服务器.想象一下,当您挂载组件时,组件要求服务器从受保护的路由中获取一些数据.服务器将提取 req 对象,获取 jwt 并使用它从受保护的路由中获取数据.浏览器和服务器获取数据的实现方式不同.如果浏览器发出请求,它只需要相对路径,但服务器需要绝对路径.如您所知,获取数据是通过组件的 getInitialProps() 完成的,并且该函数在客户端和服务器上都执行.这里是你应该如何实现它.我刚刚附上了 getInitialProps() 部分.

with verifying token process we are 100% sure if the token is valid or not. When a client asks the server to access some secret data, the client has to send the token to the server. Imagine when you mount the component, component asks the server to get some data from the protected routes. The server will extract the req object, take the jwt and use it to fetch data from the protected routes. Implementation of the fetching data for browser and server are different. And if the browser makes a request, it just needs the relative path but the server needs an absolute path. As you should know fetching data is done getInitialProps() of the component and this function executed on both client and server. here is how you should implement it. I just attached the getInitialProps() part.

MyComponent.getInitialProps = async (ctx) => {
  const another = await getSecretData(ctx.req);
 //reuslt of fetching data is passed to component as props
  return { superValue: another };
};



    const getCookieFromReq = (req, cookieKey) => {
      const cookie = req.headers.cookie
        .split(";")
        .find((c) => c.trim().startsWith(`${cookieKey}=`));

      if (!cookie) return undefined;
      return cookie.split("=")[1];
    };

   
    const setAuthHeader = (req) => {
      const token = req ? getCookieFromReq(req, "jwt") : Cookies.getJSON("jwt");

      if (token) {
        return {
          headers: { authorization: `Bearer ${token}` },
        };
      }
      return undefined;
    };

    
    export const getSecretData = async (req) => {
      const url = req ? "http://localhost:3000/api/v1/secret" : "/api/v1/secret";
      return await axios.get(url, setAuthHeader(req)).then((res) => res.data);
    };



  [1]: https://www.npmjs.com/package/jwks-rsa