如何使用受保护的路由与passport.js反应

问题描述 投票:0回答:2

使用passport.js登录后如何在react中使用受保护的路由

所以,我发现通过passport.js成功登录后,当我从前端向服务器发送请求时,请求中存在用户属性(即req.user)。

我尝试的是将 axios.get 放入应用程序组件的 componentDidMount 方法中的“/authenticate”url 中,以检查用户是否登录,并且我意识到在用户执行任何登录操作之前,componentDidMount 仅被调用一次。 我也尝试将 axios get 放入 componentDidUpdate 中,但它也无法正常工作。

在使用密码之前,我在客户端有 auth.js,只需检查 ID 和密码,如果 ID 和密码正确,则 auth.isAuthenticated 变为 true,并且可以访问受保护的路由的组件。它有效,只是当我刷新页面时,它不知何故不记得以前的 auth.isAuthenticated 并且它的初始值始终为 false 并转到“/”。 所以我转向护照并遇到了这个问题......

有人可以告诉我该怎么做吗?

'''javascript //在server.js中

app.get('/authenticate', function(req, res)  {
        if (req.user)
            res.send(true);
        else
            return null;
    });

''' '''javascript //在app.jsx中

     constructor(props) {
            super(props);
            this.state = { isAuthenticated: false };
        }


    componentDidMount() {
            axios.get('/authenticate').then((res) => {
                if (res)
                    this.setState({ isAuthenticated: true });
            })
            console.log("componentDidMount in errand-app")
            console.log(this.state.isAuthenticated);
        }

    render() {
            let { isAuthenticated } = this.state;
            return <div>
                <Switch>
                    <Route exact path="/" component={LoginPage} />
                    <ProtectedRoute isAuthenticated= 
   {this.state.isAuthenticated} exact path="/main" component={Main} />

'''

'''javascript //在受保护的路由中

const ProtectedRoute = ({ component: Component, isAuthenticated, ...rest 
}) => {
    return (
        <Route
            {...rest}
            render={props => {
                if (isAuthenticated) {
                    return <Component {...props} />;
                }
                else {
                    return (
                        <Redirect
                            to={{pathname: "/",
                                state: { from: props.location
                                 }
                            }}
                        />
                    );
                }
            }
            }
        />
    )
}

'''

我希望我的 protectedRoute 有像 isAuthenticated 这样的属性,并且只有当它为 true 时,才显示组件,否则重定向到“/”,即登录页面。

reactjs express react-router passport.js
2个回答
0
投票

我知道这是一个姗姗来迟的答案,但也许它会帮助其他正在为此苦苦挣扎的人。这个解决方案有很多步骤,但我希望它会有所帮助。

首先,为了Express和React之间的通信,我实现了JSON Web Token。出于这个问题的目的,我将省略 passport-config.js ,因为它不需要任何特殊的东西。

在快递中,server/server.js


if(process.env.NODE_ENV !== 'production') {require('dotenv').config();}
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const initializePassport = require('./passport-config');
const passport = require('passport');
const session = require('express-session');
const methodOverride = require('method-override');
const jwt = require('jsonwebtoken')
const app = express();
const bcrypt = require('bcrypt')
//const cookieParser = require('cookie-parser');
//app.use(cookieParser({secret: process.env.COOKIE_SECRET}));
app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json());
app.use(function (req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});


// JWT

const verifyJWT = (req, res, next) =>{ // This function is a JWT middleware
    const token = req.headers['x-access-token'];
    if (!token){
        res.send("Token not provided!");

    }else {
        jwt.verify(token, "jwtSecret", {},(err, decoded)=>{
            if (err){
                console.log(err);
                res.json({auth: false, message: "Token verification failed"});
            }else{
                req.userId = decoded.id;
                next();
            }
        });
    }
}


// Cors
const whitelist = process.env.WHITELISTED_DOMAINS
    ? process.env.WHITELISTED_DOMAINS.split(",")
    : []

const corsOptions = {
    origin: function (origin, callback) {
        if (!origin || whitelist.indexOf(origin) !== -1) {
            callback(null, true)
        } else {
            callback(new Error("Not allowed by CORS"))
        }
    },
    methods: ["GET", "POST", "PUT", "DELETE"],
    credentials: true,
}

app.use(cors(corsOptions))

// knex & bookshelf
const knex = require('knex')({
    client: "mysql",
    connection: {
        host: "localhost",
        user: "dbuser",
        password: "dbpassword",
        database: "cookmesomethingdb"
    }
});
const bookshelf = require('bookshelf')(knex);

async function createBookshelfOf(tbl){
    return bookshelf.Model.extend({
        tableName: tbl,
        idAttribute: "id"
    });
}

async function insertInto(table, obj){ // Helper function for INSERT
    await knex(table).insert(obj)
        .then(()=>console.log("INSERTED: " + obj + ", INTO table: " + table))
        .catch((err)=>{console.log(err); throw err;})
}

// Passport
initializePassport(
    passport,
    async username=>{
        try{
            let bookshelfUser = await createBookshelfOf("Users");
            return await new bookshelfUser().where("username", username).fetch().then((data)=>{
                return data.attributes;
            });
        }
        catch (e) {
            console.log(e);
            return null;
        }},
    async id=>{
        try{
            let bookshelfUser = await createBookshelfOf("Users");
            return await new bookshelfUser().where("idUsers", id).fetch().then((data)=>{
                return data.attributes;
            });
        }
        catch (e) {
            return null;
        }});

app.use(function (req, res, next) {
    res.header("Access-Control-Allow-Origin", req.header('origin'));
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});

/* Session*/
app.use(session({
    secret : process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized : false
}));

app.use(passport.initialize());
app.use(passport.session());
app.use(methodOverride('__method'));

// Authentication

app.post('/login', (req, res, next)=>{
    passport.authenticate('local', {
        successRedirect : '/',
        failureRedirect  : '/login'
    }, (err, user, info)=>{
        if(err){res.status(500).send(err); return;}
        if(!user){
            res.json({auth: false, message: "Could not fetch user"});
            return;
        }
        req.logIn(user, (err)=>{
            if(err){return next(err)}
        
            const id = user.idUsers;
            const token = jwt.sign({id}, "jwtSecret", {
                expiresIn: 300
            });
            let usrObj = {
                id: user.idUsers,
                username: user.username,
                name: user.name,
                surname: user.surname
            }
            res.status(200).json({auth: true, token: token, result: usrObj});
        });
    })(req, res, next);
});

app.get('/isAuth', verifyJWT, (req,res)=>{ // testing purposes
    res.send("Authentication successful")
})


app.post("/register", async(req, res) => {
    try{
        let Users = await createBookshelfOf("Users");
        let users = await new Users().fetchAll()
        for (let user of users.toJSON()){
            if (user.username === req.body.username){
                res.status(500).send("Username exists")
                return
            }
        }
        const hashedPassword = bcrypt.hash(req.body.password, 10);
        let obj = [{
            username: req.body.username,
            name: req.body.name,
            surname: req.body.surname,
            password: hashedPassword
        }];

        insertInto("Users", obj);
        res.status(200).send("Successfully inserted data")

    } catch (e) {
        console.log(e.message)
        res.status(500).send(e.message);

    }
});



// Server config

const PORT = 4000; // backend routing port
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}.`);
});

所以这部分应该是直接的。首先,您创建 JWT 验证,并在登录函数中我们首先签署令牌,然后发送数据(auth:loginStatus,token:jwt,result:userObject)

反应部分:client/components/auth.js

import {useState, createContext, useContext} from "react";
import React from 'react'
const AuthContext = createContext(null);

export const AuthProvider = ({children})=>{
    const [user, setUser] = useState(null);

    const login = (user) =>{
        setUser(user);
    }
    const logout = () =>{
        setUser(null);
    }

    return (
        <AuthContext.Provider value={{ user, login, logout}}>
            {children}
        </AuthContext.Provider>
    )
}

export const useAuth = () =>{
    return useContext(AuthContext)
}

useAuth 将允许我们检查用户是否稍后登录

然后,你需要从 Express 中检索 JWT,我使用的是 Axios。

客户端/组件/Login.js

import React from "react";
import '../css/components.css';
import { useNavigate } from 'react-router-dom'
import {useState} from "react";
import Axios from "axios";
import {useAuth} from './auth'
const styles = {
    h2Login: {
        textAlign: "center",
        fontSize: "25px"
    },
    divLogin: {
        textAlign:"center",
        paddingTop: "15px"
    },
    loginA: {
        fontSize: "15px"
    },
    margins: {
        marginTop: "5px"
    }
}


function Login(){

    const navigate = useNavigate();
    const [loginUsername, setLoginUsername] = useState("")
    const [loginPassword, setLoginPassword] = useState("")
    const [loginStatus, setLoginStatus] = useState(false);
    
    const auth = useAuth();
    const login = () => {
        Axios({
            method: "POST",
            data: {
                username: loginUsername,
                password: loginPassword,
            },
            withCredentials: true,
            url: "http://localhost:4000/login",
        }).then((res) => {
                if(res.data.auth === true){
                    setLoginStatus(true)
                    localStorage.setItem('jwt', res.data.token) // Save JWT in local storage
                    auth.login(res.data.result); // Log in with user sent from Express
                    navigate('/', {replace: true});
                }else {
                    setLoginStatus(false);
                }

            }

        ).catch((err)=>{console.log(err)});
    };


    const handleSubmit = event => {
        event.preventDefault();
    };
    const reroute = (e)=>{
        e.preventDefault();
        navigate('/register');
    }

    return(
        <section className="justify-content-center" >
            <div className="container">
                <div className="row justify-content-center">
                    <div className="col-lg-8">
                        <div>
                            <div className="form-input login-form">
                                <h2 style={styles.h2Login}>Login Form</h2>
                                <form onSubmit={handleSubmit} className="login-form">
                                    <div className="form-group">
                                        <input className="form-control form-control-md input-thingy" type="text" placeholder="username"
                                               onChange={(e) => setLoginUsername(e.target.value)}
                                        />

                                    </div>
                                    <div className="form-group ">
                                        <input className="form-control input-thingy" type="password" placeholder="password" id="password"
                                           onChange={(e) => setLoginPassword(e.target.value)}
                                        />

                                    </div>

                                        <button style={styles.margins} type="submit" className="btn btn-primary" onClick={login}>Login</button>
                                    { loginStatus &&(<button
                                    onClick={userAuthenticated}>Check if log in was successful</button>)}
                                    <div style={styles.divLogin}>
                                        <a style={styles.loginA} className="" onClick={reroute}
                                            >Don't
                                            have an account?</a>

                                    </div>

                                </form>
                            </div>
                        </div>
                    </div>
                </div>

            </div>
        </section>
    )
}



export default Login;

接下来,我们需要创建一个检查授权的函数

客户端/组件/RequireAuth.js

import  { useAuth } from "./auth";
import React from "react";
import { Navigate } from "react-router-dom";

export const RequireAuth = ({children}) => {
    const auth = useAuth();
    if (!auth.user){
        return <Navigate to='/login'/>
    }
    return children;
}

这部分只是检查用户登录时是否通过。如果身份验证失败,我们将重定向回登录页面。此时,有两种保护路由的选择。第一个选项是使用我们的 RequireAuth 包装器,在路由内部,我们只包装“受保护”组件。这会做什么,如果身份验证成功,它会将我们重定向到所需的页面或登录页面

客户端/App.js

import React from "react";
import Nav from "./components/Nav.js";
import { AuthProvider } from "./components/auth";
import {BrowserRouter as Router, Routes, Route} from "react-router-dom";
import Login from "./components/Login.js"
import Register from "./components/Register"

import Profile from "./components/Profile";
import {RequireAuth} from "./components/RequireAuth";

function App() {
    return (
        <AuthProvider>
            <Router>
            <div className="App">
                <header>
                    <Nav/>
                    <Routes>
                        <Route path='/login' element={<Login/>} />
                        <Route path='/register' element={<Register/>}/>
                        <Route path='/profile' element={<RequireAuth><Profile/></RequireAuth>}/>
                    </Routes>
                </header>
            </div>
            </Router>
        </AuthProvider>
    );
}
export default App

...或者您可以通过传递一个简单的 if 语句将路线完全隐藏在 NavBar 文件中(假设您有一个):

客户端/组件/Nav.js


import React from 'react'; //ES6
import { Link } from 'react-router-dom';
import '../css/components.css'
import {useAuth} from "./auth";

function Nav(){
    const auth = useAuth()
    return (
        <nav className="navbar navbar-expand-lg bg-transparent">
                <div className="collapse navbar-collapse" id="navbarNav">
                    <ul className="navbar-nav">
                        <li className="nav-item">
                            {
                                !auth.user && (
                                    <Link to='/login' className="nav-link">Login</Link>
                                )
                            }


                        </li>
                        <li className="nav-item">
                            {
                                !auth.user && (
                                    <Link to='/register' className="nav-link">Register</Link>
                                )
                            }
                        </li>
                        <li className="nav-item">
                            {
                                auth.user && (
                                    <Link to='/profile' className="nav-link">Profile</Link>
                                )
                            }

                        </li>

                    </ul>
                </div>
        </nav>
    );
}

export default Nav;

所以基本上你在 nav.js 文件中所做的就是说,如果用户经过身份验证,我想显示该组件。

希望有人觉得这很有用


0
投票

确保您设置了正确的参数。 以下是护照配置示例。


const LocalStrategy = require("passport-local").Strategy;
const passport = require('passport');
const session = require('express-session');
const bcrypt = require("bcryptjs");
const User = require("./../model/user/User");

function setUpPassportConfig(app) {
    const authenticateUser = async (email, password, done) => {
        try {
            const user = await User.findOne({ email });
            if (!user) {
                return done(null, false, { message: 'No user with that email' });
            }

            const isMatch = await bcrypt.compare(password, user.password);
            if (isMatch) {
                return done(null, user);
            } else {
                return done(null, false, { message: 'Password incorrect' });
            }
        } catch (e) {
            return done(e);
        }
    };

    passport.use(new LocalStrategy({ usernameField: 'email' }, authenticateUser));

    passport.serializeUser((user, done) => done(null, user.id));
    passport.deserializeUser((id, done) => {
        User.findById(id).then(user => {
            done(null, user);
        }).catch(err => {
            done(err);
        });
    });

    app.use(session({
        secret: 'secret', // Replace with a secure, randomly generated string in production
        resave: false,
        saveUninitialized: false,
        cookie: {
            httpOnly: true,
            secure: false,
            maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days in milliseconds,
            rolling: true, // This resets the cookie's maxAge countdown with each request.
            // sameSite: true
        }
    }));
    app.use(passport.initialize());
    app.use(passport.session());
}


module.exports = setUpPassportConfig;

确保您已允许 cors。另外,如果您在本地使用它,请使用 IP 地址而不是 localhost。

app.use(cors({
    origin: 'http://127.0.0.1:3000', // Replace with the URL of your front-end app
    credentials: true, // This is important for cookies, authorization headers with HTTPS 
}))

在您的 React 应用程序中,确保将 withCredentials 属性传递为 true。 这将确保会话通过客户端的 cookie 进行工作。 如果您使用的是 axios 拦截器,只需使用

request.withCredentials =true;
© www.soinside.com 2019 - 2024. All rights reserved.