使用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 时,才显示组件,否则重定向到“/”,即登录页面。
我知道这是一个姗姗来迟的答案,但也许它会帮助其他正在为此苦苦挣扎的人。这个解决方案有很多步骤,但我希望它会有所帮助。
首先,为了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 文件中所做的就是说,如果用户经过身份验证,我想显示该组件。
希望有人觉得这很有用
确保您设置了正确的参数。 以下是护照配置示例。
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;