React + NextJS - 受保护的路线
Posted
技术标签:
【中文标题】React + NextJS - 受保护的路线【英文标题】:React + NextJS - Protected routes 【发布时间】:2020-11-24 19:03:30 【问题描述】:目标:如果他/她尝试手动转到/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();
, []);
自定义 _app 组件:
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;
然后我用它包裹了组件以保护页面:
export default withAuth(NewTicket);
有没有更好的方法来处理这个问题? 非常感谢您的帮助。
【问题讨论】:
看看这个:medium.com/@eslamifard.ali/… 【参考方案1】:回答
我真的建议您查看示例以了解 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 的自定义钩子。当然,您也可以通过不同的方式来做到这一点。
这是另一个使用 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 路径或实用程序函数。
【讨论】:
我通常也是这样做的,我用 withAuth HOC 导出布局组件 如果您的登录是模态的,如何将其与 next-auth 一起使用?因为我使用的是 next-auth 的登录功能,所以它把我推到了 credential-signIn 页面?【参考方案2】:将 NextJs 升级到 9.3+ 并使用 getServerSideProps
而不是 getInitialProps
。 getServerSideProps
只在服务器端运行,与getInitialProps
不同。如果身份验证失败,则从 getServerSideProps
重定向。
【讨论】:
【参考方案3】:这是一个使用自定义“钩子”和 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)))
,
【讨论】:
【参考方案4】:只是为了扩展@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;
【讨论】:
如果认证来自后端怎么办?【参考方案5】:export const getServerSideProps = wrapper.getServerSideProps(
(store) =>
async ( req, params ) =>
const session = await getSession( req );
if (!session)
return
redirect:
destination: '/',
permanent: false,
,
;
);
在 Next 9++ 中,您可以这样做只是检查会话,如果没有,我们可以返回带有目的地的重定向以将用户路由到端点!
【讨论】:
以上是关于React + NextJS - 受保护的路线的主要内容,如果未能解决你的问题,请参考以下文章