如何在 React 和 Redux 中保持用户登录,使用 JWT 表达后端应用程序并且没有像 redux-persist 这样的包?

Posted

技术标签:

【中文标题】如何在 React 和 Redux 中保持用户登录,使用 JWT 表达后端应用程序并且没有像 redux-persist 这样的包?【英文标题】:How to persist user login in a React & Redux, express backend app with JWT and without packages like redux-persist? 【发布时间】:2020-05-07 20:16:54 【问题描述】:

所以,这就是我正在做的事情。当用户登录时,JWT 令牌设置在 localStorage 中。我正在尝试保持用户登录。登录后,注销按钮显示在标题上,但如果我刷新,我认为 redux 存储已被清除。我阅读了一些关于如何在 react 中使用 jwt 令牌验证以及 App.js 本身中的 /me 路由或 componentDidMount() 的文章,但我无法理解。任何帮助将不胜感激。

LoginForm.js

import React,  Component  from "react"
import validator from "validator"
import  loginUser  from "../actions/index"
import  connect  from "react-redux"

class LoginForm extends Component 

    constructor(props) 
        super(props)
        this.state = 
            email: "",
            password: ""
        
    

    handleChange = (event) => 
        const  name, value  = event.target
        this.setState(
            [name]: value
        )
    

    handleSubmit = (event) => 
        event.preventDefault();
        const  email, password  = this.state;

        const loginData = 
            email: this.state.email,
            password: this.state.password
        

        if (!email || !password) 
            return alert('Email and password are must.');
        

        if (password.length < 6) 
            return alert('Password must contain 6 characters.');
        

        if (!validator.isEmail(email)) 
            return alert('Invalid email.');
        

        this.props.dispatch(loginUser(loginData))
        this.props.history.push("/")
    

    render() 
        const isAuthInProgress = this.props.auth.isAuthInProgress
        return (
            <div>
                <div className="field">
                    <p className="control has-icons-left has-icons-right">
                        <input className="input" onChange=this.handleChange name="email" value=this.state.email type="email" placeholder="Email" />
                        <span className="icon is-small is-left">
                            <i className="fas fa-envelope"></i>
                        </span>
                        <span className="icon is-small is-right">
                            <i className="fas fa-check"></i>
                        </span>
                    </p>
                </div>
                <div className="field">
                    <p className="control has-icons-left">
                        <input className="input" onChange=this.handleChange name="password" value=this.state.password type="password" placeholder="Password" />
                        <span className="icon is-small is-left">
                            <i className="fas fa-lock"></i>
                        </span>
                    </p>
                    <p className="has-text-danger">Forgot password?</p>
                </div>
                <div className="field">
                    <p className="control">
                        
                            isAuthInProgress ? <p>Logging in...</p>
                        :
                        <button onClick=this.handleSubmit className="button is-success">
                            Login
                        </button>
                           
                    </p>

                </div>
            </div >
        )
    


const mapStateToProps = (state) => 
    return state



export default connect(mapStateToProps)(LoginForm)
actions/index.js

import axios from "axios"


export const registerUser = (registrationData) => 
    console.log("inside register action")
    return async dispatch => 
        dispatch( type: "REGISTRATION_STARTS" )

        try 
            const res = await axios.post("http://localhost:3000/api/v1/users/register", registrationData)
            dispatch(
                type: "REGISTRATION_SUCCESS",
                data:  user: res.data ,
            )
         catch (err) 
            dispatch(
                type: "REGISTRATION_ERROR",
                data:  error: "Something went wrong" 
            )
        
    



export const loginUser = (loginData) => 
    console.log("inside login action")
    return async dispatch => 
        dispatch( type: "AUTH_STARTS" )

        try 
            const res = await axios.post("http://localhost:3000/api/v1/users/login", loginData)
            dispatch(
                type: "AUTH_SUCCESS",
                data:  user: res.data 
            )

            localStorage.setItem("authToken", res.data.token)

         catch (err) 
            dispatch(
                type: "AUTH_ERROR",
                data:  error: "Something went wrong" 
            )
        
    

routes/users.js

router.post("/register", userController.registerUser)
router.post("/login", userController.loginUser)
router.get("/:userId", userController.getUser)
router.get("/list", userController.listUsers)

userController.js

const User = require("../models/User")
const auth = require("../utils/auth")
const validator = require("validator")

module.exports = 

    registerUser: (req, res, next) => 
        const  username, email, password  = req.body
        User.create(req.body, (err, createdUser) => 
            if (err) 
                return next(err)
             else if (!username || !email || !password) 
                return res.status(400).json( message: "Username, email and password are must" )
             else if (!validator.isEmail(email)) 
                return res.status(400).json( message: "Invaid email" )
             else if (password.length < 6) 
                return res.status(400).json( message: "Password should be of at least 6 characters" )
            
            else 
                return res.status(200).json( user: createdUser )
            
        )
    ,

    loginUser: (req, res, next) => 
        const  email, password  = req.body
        if (!email || !password) 
            return res.status(400).json(message: "Email and password are must")
        

        User.findOne( email , (err, user) => 
            if (err) 
                return next(err)
             else if (!validator.isEmail(email)) 
                return res.status(400).json( message: "Invalid email" )
             else if (!user) 
                return res.status(402).json( error: "User not found" )
             else if (!user.confirmPassword(password)) 
                return res.status(402).json( error: "Incorrect password" )
            

            // generate token here
            const token = auth.signToken(email)
            res.status(200).json( user, token )
        )
    ,


    getUser: (req, res, next) => 
        User.findById(req.params.userId, (err, user) => 
            if (err) 
                return next(err)
             else if (!user) 
                return res.status(404).json( message: "User not found" )
             else 
                return res.status(200).json( user: user )
            
        )
    ,

    listUsers: (req, res) => 
        User.find(, (err, users) => 
            if (err) 
                return res.status(404).json( error: "No users found" )
             else 
                return res.status(200).json( user: user )
            
        )
    

utils/auth.js

const jwt = require("jsonwebtoken")

function signToken(payload) 
    return jwt.sign(payload, process.env.JWTSECRET)


function verifyToken(req, res, next) 
    const token = req.headers.Authorization || req.headers.authorization || ""

    if (!token) 
        return res.status(403).json( error: "Not authorized")
    

    jwt.verify(token, process.env.JWTSECRET, (err, decoded) => 
        if (err) 
            return res.status(403).json( error: "Not authorized" )
        

        req.user = decoded
        next()
    )



module.exports =  signToken, verifyToken 

【问题讨论】:

【参考方案1】:

我认为最好的方法是使用 cookie。

在登录路由中设置cookie

    res.cookie("authToken", auth.signToken(email), 
      httpOnly: false,
     )

然后在反应端,您可以使用js-cookie 包来检索它并在后续经过身份验证的请求中使用

import Cookies from 'js-cookie';

class SomeComponent extends Component 

    constructor(props) 
        super(props)
        this.state = 
            email: "",
            password: "",
            cookie: Cookies.get('authToken')
        
    
 

【讨论】:

这种方法是不是容易受到 XSS 攻击?

以上是关于如何在 React 和 Redux 中保持用户登录,使用 JWT 表达后端应用程序并且没有像 redux-persist 这样的包?的主要内容,如果未能解决你的问题,请参考以下文章

在 react + redux + react-router-redux 中导航到用户登录成功的主页

如何使用 react-redux 实现登录认证?

无法使用react-native app中的redux-persist检查索引/主文件上是否已加载持久状态

在没有用户登录 react.js 和 redux 的情况下将商品添加到购物车

使用 React 和 react-redux-firebase 在登录期间确定范围

在 React.JS 中刷新后,如何让用户保持登录到 firebase?