如何防止Next-Auth自定义verify/error页面被直接访问?
解决Next-Auth邮箱认证中限制verify/error页面访问的问题
核心问题
当前直接访问/auth/verify或/auth/error页面时能正常加载,需要限制这些页面仅在通过signIn()触发的认证流程中才可访问。
方案一:利用状态标记+页面校验
步骤1:发起登录时添加自定义状态参数
在首页触发signIn的代码中,添加自定义state参数标记流程合法性:
// 首页登录按钮的点击逻辑 const handleSignIn = async (email: string) => { await signIn('email', { email, callbackUrl: '/dashboard', state: 'email-signin-initiated' // 自定义状态标记 }) }
步骤2:在Verify页面校验状态并拦截非法访问
修改pages/auth/verify.tsx,通过useRouter获取URL中的state参数,结合useSession判断是否来自合法流程,非法访问则重定向到首页:
import { Fragment, useEffect } from 'react'; import { Box, Flex, Heading, Text } from '@chakra-ui/react'; import { CheckCircleIcon } from '@chakra-ui/icons'; import { useSession } from 'next-auth/react'; import { useRouter } from 'next/router'; export default function Verify() { const { status } = useSession(); const router = useRouter(); const { state } = router.query; useEffect(() => { // 仅当状态为未认证且带有合法state标记时,才允许停留 if (status === 'unauthenticated' && state !== 'email-signin-initiated') { router.push('/'); } }, [status, state, router]); // 加载状态下显示占位或直接重定向 if (status === 'loading' || !state) { return <Flex minH={'100vh'} align={'center'} justify={'center'}>加载中...</Flex>; } return ( <Fragment> <Flex minH={'100vh'} align={'center'} justify={'center'}> <Box textAlign="center" py={10} px={6} maxW={'500'}> <CheckCircleIcon boxSize={'50px'} color={'green.500'} /> <Heading as="h2" size="xl" mt={6} mb={2}> 检查你的邮箱 </Heading> <Text color={'gray.500'}> 验证邮件已发送至你的邮箱,请查收并点击链接完成认证。 </Text> </Box> </Flex> </Fragment> ); }
对Error页面的同理处理
在/auth/error页面添加相同的校验逻辑,检查state参数,非法访问则重定向到首页。
方案二:将认证流程整合到登录页(无单独跳转)
如果不想单独维护verify页面,可以把整个流程放在登录页,通过组件切换实现状态展示:
// pages/index.tsx import { useState } from 'react'; import { Box, Button, Input, Flex, Heading, Text } from '@chakra-ui/react'; import { CheckCircleIcon } from '@chakra-ui/icons'; import { signIn } from 'next-auth/react'; export default function Home() { const [email, setEmail] = useState(''); const [showVerifyPrompt, setShowVerifyPrompt] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); await signIn('email', { email, callbackUrl: '/dashboard' }); setShowVerifyPrompt(true); }; if (showVerifyPrompt) { return ( <Flex minH={'100vh'} align={'center'} justify={'center'}> <Box textAlign="center" py={10} px={6} maxW={'500'}> <CheckCircleIcon boxSize={'50px'} color={'green.500'} /> <Heading as="h2" size="xl" mt={6} mb={2}> 检查你的邮箱 </Heading> <Text color={'gray.500'}> 验证邮件已发送至你的邮箱,请查收并点击链接完成认证。 </Text> </Box> </Flex> ); } return ( <Flex minH={'100vh'} align={'center'} justify={'center'}> <Box textAlign="center" py={10} px={6} maxW={'500'} borderWidth={1} borderRadius="lg"> <Heading as="h2" size="xl" mt={6} mb={2}> 登录 </Heading> <form onSubmit={handleSubmit}> <Input placeholder="输入你的邮箱" value={email} onChange={(e) => setEmail(e.target.value)} mb={4} type="email" required /> <Button type="submit" colorScheme="blue" width="full"> 发送验证邮件 </Button> </form> </Box> </Flex> ); }
这种方式不存在单独的verify页面,自然避免了直接访问的问题,适合流程简单的场景。
内容的提问来源于stack exchange,提问作者SudoGaron
相关产品推荐
相关产品推荐

