React + NextJS -受保护的路由

2g32fytz  于 2023-02-12  发布在  React
关注(0)|答案(8)|浏览(139)
    • 目标:**我希望在 * 已登录用户 * 尝试手动转到/auth/signin时将其重定向到主页。

登录页面/组件:

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();
    }, []);

自定义应用程序组件(_A):

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条件,但这是一种变通方法,处理此问题的最佳方法/实践是什么?
我想出了另一个解决办法:

  • 我建立了一个重定向钩子:
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);
    }
}
  • 然后,我做了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;
  • 然后我用它 Package 组件以保护页面:
export default withAuth(NewTicket);

有什么更好的方法来处理这个问题吗?我真的很感激你的帮助。

eqzww0vc

eqzww0vc1#

答案

我真的建议你看看例子,看看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

因此,上面的选项是默认服务器端呈现的应用程序,因为我们使用/api路径进行身份验证。如果你想要一个无服务器的解决方案,你可能必须将/api路径中的所有内容拉到一个lambda(AWS Lambda)+一个网关api(AWS Api Gateway)中。你所需要的只是一个连接到该api的自定义钩子。当然,你也可以通过不同的方式来实现这一点。
下面是另一个使用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
🙌

意见

custom_app组件通常是一个顶级 Package 器(top_document并不完全符合这种描述)。
实际上,我会在_app下面创建一个Login组件,通常我会在Layout组件中实现该模式,或者像上面的示例一样,使用api路径或实用程序函数。

mw3dktmi

mw3dktmi2#

下面是一个使用getServerSideProps的自定义"钩子"的示例。
我使用的是react-query,但是您可以使用任何数据获取工具。

// /pages/login.jsx

import SessionForm from '../components/SessionForm'
import { useSessionCondition } from '../hooks/useSessionCondition'

export const getServerSideProps = useSessionCondition(false, '/app')

const Login = () => {
    return (
        <SessionForm isLogin/>
    )
}

export default Login
// /hooks/useSessionCondition.js

import { QueryClient } from "react-query";
import { dehydrate } from 'react-query/hydration'
import { refreshToken } from '../utils/user_auth';

export const useSessionCondition = (
    sessionCondition = true, // whether the user should be logged in or not
    redirect = '/' // where to redirect if the condition is not met
) => {

    return async function ({ req, res }) {
        const client = new QueryClient()
        await client.prefetchQuery('session', () => refreshToken({ headers: req.headers }))
        const data = client.getQueryData('session')

        if (!data === sessionCondition) {
            return {
                redirect: {
                    destination: redirect,
                    permanent: false,
                },
            }
        }

        return {
            props: {
                dehydratedState: JSON.parse(JSON.stringify(dehydrate(client)))
            },
        }
    }

}
sauutmhj

sauutmhj3#

将NextJs升级到9.3+并使用getServerSideProps而不是getInitialPropsgetServerSideProps仅运行,并且始终在服务器端运行,这与getInitialProps不同。如果身份验证失败,请从getServerSideProps重定向。

aiazj4mn

aiazj4mn4#

export const getServerSideProps = wrapper.getServerSideProps(
  (store) =>
    async ({ req, params }) => {
      const session = await getSession({ req });

      if (!session) {
        return {
          redirect: {
            destination: '/',
            permanent: false,
          },
        };
      }
    }
);

在这里,在未来9++你可以这样做,只是检查会话,如果没有,我们可以返回一个目的地重定向路由用户到终点!

hzbexzde

hzbexzde5#

我想进一步解释一下@Nico在他的评论中所说的话。下面是我的设置:Layout.tsx文件

// ...
import withAuth from "../utils/withAuth";

interface Props {
  children?: ReactNode;
  title?: string;
}

const Layout = ({
  children,
  title = "This is the default title",
}: Props): JSX.Element => (
  <>
    {children}
  </>
);

export default withAuth(Layout);

withAuth.js文件

import { getSession } from "next-auth/client";

export default function withAuth(Component) {
  const withAuth = (props) => {
    return <Component {...props} />;
  };

  withAuth.getServerSideProps = async (ctx) => {
    return { session: await getSession(ctx) };
  };

  return withAuth;
}
rxztt3cl

rxztt3cl6#

我也遇到过同样的问题,我的客户端解决方案不 Flink 的内容如下:如果我用错了方法,请纠正我。我使用useRouter

//@/utils/ProtectedRoute
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
export const ProtectedRoute = ({ user = false, children }) => {
  const [login, setLogin] = useState(false);
  const router = useRouter();

  useEffect(() => {
    login && router.push("/account/login");//or router.replace("/account/login");
  }, [login]);
  useEffect(() => {
    !user && setLogin(true);
  }, []);

  return (
     <>
      {user ? (
        children
       ) : (
        <div>
          <h4>
            You are not Authorized.{" "}
            <Link href="/account/login">
              <a>Please log in</a>
            </Link>
          </h4>
        </div>
      )}
    </>
  };
)

当我想要保护路由时,我使用以下语法:

import { ProtectedRoute } from "@/utils/ProtectedRoute";
const ProtectedPage = () => {
  const user = false;
  return (
    <ProtectedRoute user={user}>
      <h1>Protected Content</h1>
    </ProtectedRoute>
  );
};

export default ProtectedPage;
aamkag61

aamkag617#

// Authentication.js

import { useRouter } from "next/router";
import React, { useEffect } from "react";

function Authentication(props) {
  let userDetails;
  const router = useRouter();
  useEffect(() => {
    if (typeof window !== undefined) {
      userDetails=useSession
      if (!userDetails) {
        const path = router.pathname;
        switch (path) {
          case "/":
            break;
          case "/about":
            break;
          case "/contact-us":
            break;
          default:
            router.push("/");
        }
      } else if (userDetails) {
        if (router.pathname == "/") {
          router.push("/home");
        }
      }
    }
  }, []);
  return <>{props.children}</>;
}

export default Authentication;

现在将此代码添加到您的_app. js中

<DefaultLayout>
      <Authentication>
        <Component {...pageProps} />
      </Authentication>
 </DefaultLayout>

Evrything现在应该工作,如果你想添加加载。

mu0hgdu0

mu0hgdu08#

Vercel最近引入了middleware for Next.js.Next.js中间件,允许您在处理HTTP请求之前运行代码。
要将中间件逻辑添加到应用,请在Next.js项目的根目录中添加middleware.jsmiddleware.ts文件。

export async function middleware(req: NextRequest) {
  const token = req.headers.get('token') // get token from request header
  const userIsAuthenticated = true // TODO: check if user is authenticated

  if (!userIsAuthenticated) {
    const signinUrl = new URL('/signin', req.url)
    return NextResponse.redirect(signinUrl)
  }

  return NextResponse.next()
}

// Here you can specify all the paths for which this middleware function should run
// Supports both a single string value or an array of matchers
export const config = {
  matcher: ['/api/auth/:path*'],
}

相关问题