reactjs - React + NextJS - protected 路线

标签 reactjs authentication next.js server-side-rendering next-router

目标:如果他/她尝试手动转到/auth/signin,我想将登录用户重定向到主页。

Signin page/component :


const Signin = ({ currentUser }) => {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const { doRequest, errors } = useRequest({
        url: '/api/users/signin',
        method: 'post',
        body: {
            email, password
        },
        onSuccess: () => Router.push('/')
    });

    useEffect(() => {
        const loggedUser = () => {
            if (currentUser) {
                Router.push('/');
            }
        };
        loggedUser();
    }, []);

Custom _app component:


const AppComponent = ({ Component, pageProps, currentUser }) => {
    return (
        <div>
            <Header currentUser={currentUser} />
            <Component {...pageProps} currentUser={currentUser} />
        </div>

    )
};

AppComponent.getInitialProps = async (appContext) => {
    const client = buildClient(appContext.ctx);
    const { data } = await client.get('/api/users/currentuser');
    let pageProps = {};
    if (appContext.Component.getInitialProps) {
        pageProps = await appContext.Component.getInitialProps(appContext.ctx);
    }
    return {
        pageProps,
        ...data
    }
};

export default AppComponent;
问题 :
我试过这个,但这会导致轻微的延迟,因为它不会在服务器端呈现。
延迟是指:它会在重定向之前显示我不想显示的页面一秒钟左右。
我可以使用加载标志或一堆 if else 条件,但这可以解决, 是什么?最佳方法/实践处理这个问题?

Another solution I came up with:


  • 我建立了一个重定向钩子(Hook):

  • import Router from 'next/router';
    export default (ctx, target) => {
        if (ctx.res) {
            // server 
            ctx.res.writeHead(303, { Location: target });
            ctx.res.end();
        } else {
            // client
            Router.push(target);
        }
    }
    
  • 然后我为 protected 路由创建了 2 个 HOC(用于登录和注销的用户):

  • import React from 'react';
    import redirect from './redirect';
    const withAuth = (Component) => {
        return class AuthComponent extends React.Component {
            static async getInitialProps(ctx, { currentUser }) {
                if (!currentUser) {
                    return redirect(ctx, "/");
                }
            }
            render() {
                return <Component {...this.props} />
            }
        }
    }
    export default withAuth;
    
  • 然后我用它包裹了组件​​以保护页面:

  • export default withAuth(NewTicket);
    
    有没有更好的方法来处理这个?
    非常感谢您的帮助。

    最佳答案

    回答
    我真的建议您查看示例以了解 NextJS 建议如何处理此问题。资源真的很好!
    https://github.com/vercel/next.js/tree/master/examples
    例如,您可以使用 next-auth 这是一个开源身份验证选项。
    例子在这里。 https://github.com/vercel/next.js/tree/master/examples/with-next-auth

    // _app.js
    import { Provider } from 'next-auth/client'
    import '../styles.css'
    
    const App = ({ Component, pageProps }) => {
      const { session } = pageProps
      return (
        <Provider options={{ site: process.env.SITE }} session={session}>
          <Component {...pageProps} />
        </Provider>
      )
    }
    
    export default App
    
    // /pages/api/auth/[...nextauth].js
    import NextAuth from 'next-auth'
    import Providers from 'next-auth/providers'
    
    const options = {
      site: process.env.VERCEL_URL,
      providers: [
        Providers.Email({
          // SMTP connection string or nodemailer configuration object https://nodemailer.com/
          server: process.env.EMAIL_SERVER,
          // Email services often only allow sending email from a valid/verified address
          from: process.env.EMAIL_FROM,
        }),
        // When configuring oAuth providers make sure you enabling requesting
        // permission to get the users email address (required to sign in)
        Providers.Google({
          clientId: process.env.GOOGLE_ID,
          clientSecret: process.env.GOOGLE_SECRET,
        }),
        Providers.Facebook({
          clientId: process.env.FACEBOOK_ID,
          clientSecret: process.env.FACEBOOK_SECRET,
        }),
        Providers.Twitter({
          clientId: process.env.TWITTER_ID,
          clientSecret: process.env.TWITTER_SECRET,
        }),
        Providers.GitHub({
          clientId: process.env.GITHUB_ID,
          clientSecret: process.env.GITHUB_SECRET,
        }),
      ],
      // The 'database' option should be a connection string or TypeORM
      // configuration object https://typeorm.io/#/connection-options
      //
      // Notes:
      // * You need to install an appropriate node_module for your database!
      // * The email sign in provider requires a database but OAuth providers do not
      database: process.env.DATABASE_URL,
    
      session: {
        // Use JSON Web Tokens for session instead of database sessions.
        // This option can be used with or without a database for users/accounts.
        // Note: `jwt` is automatically set to `true` if no database is specified.
        // jwt: false,
        // Seconds - How long until an idle session expires and is no longer valid.
        // maxAge: 30 * 24 * 60 * 60, // 30 days
        // Seconds - Throttle how frequently to write to database to extend a session.
        // Use it to limit write operations. Set to 0 to always update the database.
        // Note: This option is ignored if using JSON Web Tokens
        // updateAge: 24 * 60 * 60, // 24 hours
        // Easily add custom properties to response from `/api/auth/session`.
        // Note: This should not return any sensitive information.
        /*
        get: async (session) => {
          session.customSessionProperty = "ABC123"
          return session
        }
        */
      },
    
      // JSON Web Token options
      jwt: {
        // secret: 'my-secret-123', // Recommended (but auto-generated if not specified)
        // Custom encode/decode functions for signing + encryption can be specified.
        // if you want to override what is in the JWT or how it is signed.
        // encode: async ({ secret, key, token, maxAge }) => {},
        // decode: async ({ secret, key, token, maxAge }) => {},
        // Easily add custom to the JWT. It is updated every time it is accessed.
        // This is encrypted and signed by default and may contain sensitive information
        // as long as a reasonable secret is defined.
        /*
        set: async (token) => {
          token.customJwtProperty = "ABC123"
          return token
        }
        */
      },
    
      // Control which users / accounts can sign in
      // You can use this option in conjunction with OAuth and JWT to control which
      // accounts can sign in without having to use a database.
      allowSignin: async (user, account) => {
        // Return true if user / account is allowed to sign in.
        // Return false to display an access denied message.
        return true
      },
    
      // You can define custom pages to override the built-in pages
      // The routes shown here are the default URLs that will be used.
      pages: {
        // signin: '/api/auth/signin',  // Displays signin buttons
        // signout: '/api/auth/signout', // Displays form with sign out button
        // error: '/api/auth/error', // Error code passed in query string as ?error=
        // verifyRequest: '/api/auth/verify-request', // Used for check email page
        // newUser: null // If set, new users will be directed here on first sign in
      },
    
      // Additional options
      // secret: 'abcdef123456789' // Recommended (but auto-generated if not specified)
      // debug: true, // Use this option to enable debug messages in the console
    }
    
    const Auth = (req, res) => NextAuth(req, res, options)
    
    export default Auth
    
    所以上面的选项是 defo 一个服务器端渲染的应用程序,因为我们使用/api 路径进行身份验证。如果您想要一个无服务器解决方案,您可能必须将/api 路径中的所有内容拉入 lambda (AWS Lambda) + 网关 api (AWS Api Gateway)。您只需要一个连接到该 api 的自定义钩子(Hook)。当然,您也可以通过不同的方式做到这一点。
    这是另一个使用 firebase 的身份验证示例。
    https://github.com/vercel/next.js/tree/master/examples/with-firebase-authentication
    另一个使用 Passport.js 的例子
    https://github.com/vercel/next.js/tree/master/examples/with-passport
    您还询问了加载行为,这个示例可能会对您有所帮助
    https://github.com/vercel/next.js/tree/master/examples/with-loading
    🙌
    观点
    自定义 _app 组件通常是顶级包装器(不是最顶级的 _document 适合该描述)。
    实际上,我会在 _app 下一级创建一个登录组件。通常我会在 Layout 组件中实现该模式,或者像上面的示例一样,使用 api 路径或实用程序函数。

    关于reactjs - React + NextJS - protected 路线,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63251020/

    相关文章:

    logging - 通过从PC浏览器捕获二维码在移动设备上登录我的网站

    javascript - setState 不更新嵌套对象

    javascript - api调用后更新组件的正确方法是什么

    css - 使最后一个网格单元格处于非事件状态

    javascript - 文本内容与使用 i18n 进行翻译的服务器呈现的 HTML 不匹配

    reactjs - Next.js 部署 : Node. js 与静态 HTML 导出?

    npm - 如何将 node.js 模块与 Next.js 一起使用?

    javascript - 使用 BrowserRouter 响应空闲计时器

    ruby-on-rails - strip 连接身份验证失败 - invalid_client : No such API key: Bearer

    PHP检查用户是否使用函数登录