You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 20:19:49