NextJS注册流程中JWT令牌过期导致提交失败的问题解决请求
问题:注册时JWT令牌过期导致请求失败
我基于NextJS Pages Router与TypeScript实现注册流程,用户需输入用户名、未在数据库中存在的邮箱、密码及确认密码。当前遇到的问题是:JWT令牌有效期为15分钟,若用户在页面停留超过15分钟后提交注册表单,将无法完成注册。我希望在register()函数中调用getTokens(),确保在发送注册POST请求前执行该函数以刷新令牌,但此前尝试使用async/await未奏效,请求协助解决。后续另有2个相关注册问题需请教。
相关代码
Register组件代码
import React, { useState } from 'react' import styles from './styles.module.css' import { Header } from '@/components' import Head from 'next/head' import { hash } from '@/scripts/hashing' import axios from 'axios' import { getTokens } from '@/scripts/tokens' import { store } from '@/redux/store' import Link from 'next/link' import { useDispatch } from 'react-redux' import { setAccessToken, setRefreshToken } from '@/redux/constants/jwtTokensReducer' const Register = () => { const [name, setName] = React.useState('') const [email, setEmail] = useState('') const [emailExists, setEmailExists] = useState(false) const [password, setPassword] = useState('') const [repeatedPassword, setRepeatedPassword] = useState('') const [passwordsMatch, setPasswordsMatch] = useState(true) const dispatch = useDispatch() const checkEmailExists = async () => { if(email) { await getTokens(dispatch) console.log(store.getState().jwtTokens.accessToken) await axios.get(`${process.env.API_URL}/api/email=${email}`, { headers: { token: store.getState().jwtTokens.accessToken, } }).then(res => { if(res.data !== null) { setEmailExists(true) } else { setEmailExists(false) } }) } else { setEmailExists(false) } } const checkPasswordsMatch = async () => { // this function compares passwords and works as expected setPasswordsMatch(password === repeatedPassword) } const register = async (e: any) => { e.preventDefault() checkEmailExists() checkPasswordsMatch() console.log(emailExists) if(!emailExists && passwordsMatch) { const hashedPass = await hash(password) console.log('Hashed Password:', hashedPass) const restaurant = { name: name, email: email, email_confirmed: false, password: hashedPass, created: `${new Date().getDay()}.${new Date().getMonth()}.${new Date().getFullYear()}`, cuisineType: cuisineType, address: address, phoneNumber: phoneNumber, priceRange: priceRange, } console.log('aaa') await getTokens(dispatch) console.log(store.getState().jwtTokens.accessToken) console.log('bbb') axios.post(`${process.env.API_URL}/api/restaurants/register`, { restaurant: restaurant }, { headers: { token: store.getState().jwtTokens.accessToken,}}).then(() => console.log('registered')) } } return ( <> <Head> <title>Registration</title> </Head> <div className={`${styles.main}`}> <Header /> <div className={`${styles.Registration}`}> <p className={`${styles.RegistrationTitle}`}>Register</p> <form className={`${styles.RegistrationForm}`} onSubmit={register}> <div className={`${styles.RegistrationReg_LogBox}`}> <Link href={`/login`} className={`${styles.RegistrationLogBox}`}>Login</Link> <Link href={`/registration`} className={`${styles.RegistrationRegBox}`}>Register</Link> </div> <div> <input className={`${styles.RegistrationInput}`} type='text' onChange={e => setName(e.target.value)} placeholder='Restaurant name*' required/> </div> <div> <input className={`${styles.RegistrationInput} ${emailExists && styles.RegistrationWarn}`} type='email' onChange={e => setEmail(e.target.value)} placeholder='email*' onFocus={() => setEmailExists(false)} onBlur={checkEmailExists} required/> {emailExists && <p className={`${styles.RegistrationErrorText}`}>Email is already used</p>} </div> <div> <input className={`${styles.RegistrationInput} ${!passwordsMatch && styles.RegistrationWarn}`} type='password' autoComplete='new-password' onChange={e => setPassword(e.target.value)} placeholder='password*' onBlur={checkPasswordsMatch} onFocus={() => setPasswordsMatch(true)} required minLength={8}/> <p className={`${styles.RegistrationInfoText}`}>Passwords needs to be at least 8 char long</p> </div> <div> <input className={`${styles.RegistrationInput} ${!passwordsMatch && styles.RegistrationWarn}`} type='password' autoComplete='new-password' onChange={e => setRepeatedPassword(e.target.value)} placeholder='confirm password*' onBlur={checkPasswordsMatch} onFocus={() => setPasswordsMatch(true)} required minLength={8}/> {!passwordsMatch && <p className={`${styles.RegistrationErrorText}`}>Passwords needs to match</p>} </div> <button type='submit' className={`${styles.RegistrationSubmitButton}`}>Register</button> <p className={`${styles.RegistrationFormNoAccount}`}>Already have an account? <span className={`${styles.RegistrationFormHighlightedText}`}> <Link href={`/login`}>Login now</Link></span></p> </form> </div> </div> </> ) } export default Register
getTokens函数代码
import axios from 'axios' import { setAccessToken, setRefreshToken } from '@/redux/constants/jwtTokensReducer' import { store } from '@/redux/store' import { hash } from '@/scripts/hashing' import { encrypt } from './encryption' import { Dispatch } from 'redux' export const getTokens = async (dispatch: Dispatch) => { axios.post(`${process.env.API_URL}/api/web/jwt-action`, { accessToken: store.getState().jwtTokens.accessToken, refreshToken: store.getState().jwtTokens.refreshToken, }) .then(data => { if(data.data.accessToken) { dispatch({type: setAccessToken, payload: data.data.accessToken}) } if(data.data.refreshToken) { dispatch({type: setRefreshToken, payload: data.data.refreshToken}) } }) }
解决方案
1. 修复getTokens()的异步逻辑
你的getTokens()函数声明了async但未返回Promise,导致await无法等待令牌刷新完成。修改如下:
export const getTokens = async (dispatch: Dispatch) => { // 等待请求完成并返回响应 const response = await axios.post(`${process.env.API_URL}/api/web/jwt-action`, { accessToken: store.getState().jwtTokens.accessToken, refreshToken: store.getState().jwtTokens.refreshToken, }) // 更新Redux中的令牌状态 if(response.data.accessToken) { dispatch({type: setAccessToken, payload: response.data.accessToken}) } if(response.data.refreshToken) { dispatch({type: setRefreshToken, payload: response.data.refreshToken}) } }
2. 修正register()中的异步执行顺序
当前register()里调用checkEmailExists()和checkPasswordsMatch()时未加await,导致状态还未更新就进入判断逻辑。同时要确保令牌刷新完成后再发送注册请求:
const register = async (e: any) => { e.preventDefault() // 等待邮箱检查和密码匹配验证完成 await checkEmailExists() await checkPasswordsMatch() // 验证通过后再执行后续逻辑 if(!emailExists && passwordsMatch) { const hashedPass = await hash(password) // 补全未定义的状态变量(需在组件顶部添加对应useState) const [cuisineType, setCuisineType] = useState('') const [address, setAddress] = useState('') const [phoneNumber, setPhoneNumber] = useState('') const [priceRange, setPriceRange] = useState('') const restaurant = { name: name, email: email, email_confirmed: false, password: hashedPass, // 修正日期格式:getDay()是星期数,getDate()才是日期;getMonth()从0开始,需+1 created: `${new Date().getDate()}.${new Date().getMonth() + 1}.${new Date().getFullYear()}`, cuisineType: cuisineType, address: address, phoneNumber: phoneNumber, priceRange: priceRange, } // 等待令牌刷新完成 await getTokens(dispatch) // 发送注册请求,使用最新的令牌 try { await axios.post(`${process.env.API_URL}/api/restaurants/register`, { restaurant: restaurant }, { headers: { token: store.getState().jwtTokens.accessToken } }) console.log('registered') // 可添加注册成功后的逻辑,比如跳转到登录页 } catch (error) { console.error('注册失败:', error) // 处理错误,比如提示用户令牌刷新失败或注册出错 } } }
3. 优化:使用useSelector获取Redux状态
避免直接调用store.getState(),改用useSelector让组件自动响应状态变化:
import { useSelector } from 'react-redux' // 在组件内获取令牌 const { accessToken } = useSelector(state => state.jwtTokens) // 发送请求时直接使用该变量 headers: { token: accessToken }
内容的提问来源于stack exchange,提问作者R3dragon
相关产品推荐
相关产品推荐

