Expo+Node+SendGrid密码重置:如何跳转客户端并传递用户标识
密码重置功能实现方案(React Native Expo + Node.js)
完全可以只在客户端保留改密表单,点击邮件链接直接跳转至客户端并获取用户标识,核心是用Expo深度链接携带加密的用户验证信息,以下是具体实现步骤和代码修正:
一、后端调整(Node.js)
核心是生成安全的短期重置令牌(而非明文传邮箱/ID),构造正确的带参深度链接,修复邮件发送逻辑:
const sgMail = require('@sendgrid/mail'); const jwt = require('jsonwebtoken'); // 需先安装:npm install jsonwebtoken const bcrypt = require('bcrypt'); // 密码加密用,需安装:npm install bcrypt sendResetPasswordLink: async (email, baseLink) => { sgMail.setApiKey(process.env.SENDGRID_API_KEY); // 1. 生成1小时有效期的重置令牌,包含用户邮箱信息 const resetToken = jwt.sign( { email: email }, process.env.JWT_RESET_SECRET, // 自己在.env里配置密钥 { expiresIn: '1h' } ); // 2. 拼接带令牌的深度链接 const resetLink = `${baseLink}?token=${resetToken}`; // 3. 发送邮件(替换硬编码的邮箱和内容) const msg = { to: email, // 用传入的目标用户邮箱 from: process.env.SENDER_EMAIL, // 替换成SendGrid验证过的发件邮箱 subject: '重置你的账号密码', text: `点击链接重置密码:${resetLink}`, html: `<p>请点击下面的链接重置你的密码(1小时内有效):</p><a href="${resetLink}">立即重置密码</a>`, }; try { await sgMail.send(msg); return { success: true }; } catch (error) { console.error(error); return { success: false, error: error.message }; } }
二、客户端调整(React Native Expo)
需要监听深度链接打开事件,解析令牌并跳转改密页面,提交新密码时携带令牌验证:
1. 根组件监听链接事件(比如App.js)
import { Linking, useEffect } from 'react-native'; import { useNavigation } from '@react-navigation/native'; function App() { const navigation = useNavigation(); useEffect(() => { // 处理应用已打开时的链接跳转 const handleOpenURL = async (event) => { const url = event.url; const params = new URLSearchParams(url.split('?')[1]); const resetToken = params.get('token'); if (resetToken) { navigation.navigate('ChangePassword', { resetToken }); } }; // 监听链接打开事件 Linking.addEventListener('url', handleOpenURL); // 处理应用未启动时,通过链接直接打开的情况 const checkInitialURL = async () => { const initialURL = await Linking.getInitialURL(); if (initialURL) handleOpenURL({ url: initialURL }); }; checkInitialURL(); return () => Linking.removeEventListener('url', handleOpenURL); }, [navigation]); // ...其他组件逻辑 }
2. 请求发送重置链接的代码修正
// 生成指向改密页面的基础深度链接 const baseLink = Linking.createURL('/ChangePassword'); // 传给后端邮箱和基础链接 axios.post('/send_reset_password_link', { email, baseLink }) .then(() => alert('重置链接已发送到你的邮箱')) .catch(err => alert('发送失败,请稍后重试'));
3. 改密页面(ChangePassword.js)
import { useState } from 'react'; import { View, TextInput, Button, Alert } from 'react-native'; import axios from 'axios'; const ChangePassword = ({ route, navigation }) => { const { resetToken } = route.params; const [newPassword, setNewPassword] = useState(''); const handleReset = async () => { if (!newPassword.trim()) return Alert.alert('提示', '请输入新密码'); try { await axios.post('/reset_password', { token: resetToken, newPassword: newPassword }); Alert.alert('成功', '密码重置完成,请重新登录'); navigation.navigate('Login'); } catch (err) { Alert.alert('失败', err.response?.data?.message || '链接已过期或无效'); } }; return ( <View style={{ padding: 20, gap: 15 }}> <TextInput placeholder="输入新密码" secureTextEntry value={newPassword} onChangeText={setNewPassword} style={{ borderBottomWidth: 1, padding: 10 }} /> <Button title="确认重置密码" onPress={handleReset} /> </View> ); }; export default ChangePassword;
三、后端新增密码重置验证接口
const jwt = require('jsonwebtoken'); const bcrypt = require('bcrypt'); const User = require('./models/User'); // 你的用户数据模型 app.post('/reset_password', async (req, res) => { const { token, newPassword } = req.body; try { // 验证令牌有效性 const decoded = jwt.verify(token, process.env.JWT_RESET_SECRET); const user = await User.findOne({ email: decoded.email }); if (!user) return res.status(404).json({ message: '用户不存在' }); // 加密新密码后更新 const hashedPwd = await bcrypt.hash(newPassword, 10); user.password = hashedPwd; await user.save(); res.status(200).json({ message: '密码重置成功' }); } catch (err) { if (err.name === 'TokenExpiredError') { return res.status(400).json({ message: '重置链接已过期' }); } res.status(400).json({ message: '无效的重置链接' }); } });
关键注意事项
- 绝对不要明文传递用户ID/邮箱,必须用加密令牌,防止恶意篡改
- 生产环境建议用Expo通用链接(Universal Links)替代本地IP的exp链接,提升兼容性
- 令牌有效期设置短一些(1-2小时),降低安全风险
- 所有用户密码必须哈希加密后存储,禁止明文保存
内容的提问来源于stack exchange,提问作者learncode
相关产品推荐
相关产品推荐

