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

React路由鉴权失效求助:未登录可直接访问Dashboard页面

问题描述

我正在开发一个网站,已完成注册页面、登录页面和Dashboard页面。我希望用户仅在通过登录页面成功登录后,才能访问Dashboard页面,但目前直接在地址栏输入'/dashboard'就能显示该页面。请帮忙指出代码中的问题。


后端路由文件代码

const express = require ("express")
const Resident = require("../models/residentModels");
const bcrypt = require("bcrypt");
const auth = require("../middleware/auth");

const router = express.Router();

router.post("/register", async (req, res) => {
    try {
        const resident = new Resident(req.body);

        console.log(req.body);

        // password hash 
        // middleware
        // const token = await resident.generateAuthToken();

        // console.log("token: " + token);

        // res.cookie("jwt", token, {
        //     httpOnly: true,
        //     expires: new Date (Date.now() + 12342)
        // })
        // console.log(cookie);

        const newResident = await resident.save();

        console.log("req.cookies.jwt: " + req.cookies.jwt);

        console.log("hehe" + newResident);
        // console.log(newResident._id);
        // console.log(newResident._id.toString());

        res.status(200).send("Record Added Successfully");
    } catch (error) {
        console.log(error);
        res.status(200).send("Record Creation Unsuccessfull");
    }
})

router.post("/login", async (req, res) => {
    try {
        let blockNumber = req.body.blockNumber;
        let flatNumber = req.body.flatNumber;
        let password = req.body.password;

        // console.log(blockNumber)
        // console.log(flatNumber)
        // console.log(password)

        const response = await Resident.findOne({blockNumber: blockNumber, flatNumber:flatNumber});

        console.log("response: " + response);

        const isMatch = await bcrypt.compare(password, response.password);

        console.log("isMatch: " + isMatch);

        // const token = await response.generateAuthToken();
        // console.log(token);

        const token = await response.generateAuthToken();
        console.log("token part: " + token);

        res.cookie("jwt", token, {
            httpOnly: true,
            expires: new Date(Date.now() + 300000)
            // secure: true
        })
        // console.log(cookie)
        // res.header("Authorization", token);

        console.log(`this is the cookie awesome !! + ${req.cookies.jwt}`); // will return undefined as same page generating cookies will not access it

        if(isMatch){
            res.status(200).send("Successful Login");
        }
        else{
            // res.status(200).send("Invalid Login Details");
        }
        res.status(200).send({ user, token: jwt.token });
    } catch (error) {
        // res.status(200).send("Invalid Login Details");
    }
});

router.get("/dashboard", auth, async(req, res) => {
    console.log("token: " + req.cookies.jwt);
    res.status(200).send(req.cookies.jwt);
})

router.get("/logout", auth, async(req, res) => {
    try {
        console.log("logout successfully");
    } catch (error) {
        res.status(500).send(error);
    }
})

router.get("/festivals", auth, async(req, res) => {
    console.log(req.cookies.jwt);
    res.status(200).send(req.cookies.jwt);
})

module.exports = router;

React前端App.js文件代码

import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Home from './components/pages/Utility/Home';
import Gallery from './components/pages/Utility/Gallery';
import Teams from './components/pages/Utility/Teams';
import Login from './components/pages/Resident/Login';
import Register from './components/pages/Resident/Register';
import Dashboard from './components/pages/Resident/Dashboard';
import Festivals from './components/pages/Resident/Festivals';
import DurgaPuja from './components/pages/Resident/DurgaPuja';
import DurgaPujaBhog from './components/pages/Resident/DurgaPujaBhog';
import QRCode from './components/pages/Resident/QRCode';
import AdminLogin from './components/pages/Admin/AdminLogin';
import AdminPanel from './components/pages/Admin/AdminPanel';
import AdminPayment from './components/pages/Admin/AdminPayment';
import AdminBhogDistribution from './components/pages/Admin/AdminBhogDistribution';
import Navbar from './components/Navbar';
import Logout from './components/pages/Utility/Logout';
import AuthApi from './utils/AuthApi';
import { useState } from 'react';

function App() {

  const [auth, setAuth] = useState(false);

  return (
    <>
      {/* <AuthApi.Provider value={{auth, setAuth}}> */}
        <BrowserRouter>
          <Navbar />
          <Routes>
            <Route path='/' element={<Home />} />
            <Route path='/home' element={<Home />} />
            <Route path='/gallery' element={<Gallery />} />
            <Route path='/team' element={<Teams />} />
            <Route path='/login' element={<Login />} />
            <Route path='/register' element={<Register />} />
            <Route path='/dashboard' element={<Dashboard />} />
            <Route path='/festivals' element={<Festivals />} />
            <Route path='/durga-puja' element={<DurgaPuja />} />
            <Route path='/durga-puja-bhog' element={<DurgaPujaBhog />} />
            <Route path='/qr-code' element={<QRCode />} />
            <Route path='/logout' element={<Logout />} />

            {/* Admin Panel */}
            <Route path='/admin-login' element={<AdminLogin />} />
            <Route path='/admin-panel' element={<AdminPanel />} />
            <Route path='/admin-payment' element={<AdminPayment />} />
            <Route path='/admin-bhog-distribution' element={<AdminBhogDistribution />} />

          </Routes>
        </BrowserRouter>

      {/* </AuthApi.Provider> */}
    </>
  );
}

export default App;

Login.jsx文件代码

import axios from 'axios';
import React from 'react'
import { NavLink, useNavigate } from 'react-router-dom'

const Login = () => {

  let navigate = useNavigate();

  const loginButton = async (event) => {
    event.preventDefault();

    // console.log(event.target[0].value); // block number

    let blockNumber = event.target[0].value;
    let flatNumber = event.target[1].value;
    let password = event.target[2].value;

    const response = await axios.post('http://localhost:8000/login', {
      blockNumber: blockNumber,
      flatNumber: flatNumber,
      password: password
    }, {
      withCredentials: true
    })

    let myToken = axios.get("http://localhost:8000/dashboard", {
      withCredentials: true
    }).then((res)=> console.log(res.data));

    console.log(myToken);

    // console.log("response :" + response.data);
    console.log(response);

    if (response.status === 200) {
      alert("Login Successful");
      navigate("/dashboard");
    }
    else {
      alert("Invalid Login Details");
      navigate("/dashboard");
    }
  }

  return (
    <>
      <h1> Login </h1>
      <form onSubmit={loginButton}>
        <input type="number" placeholder='Block Number *' />
        <input type="number" placeholder='Flat Number *' />
        <input type="password" placeholder='Password *' />

        <button type="submit">Login</button>
        <NavLink to='/register'>or Click to Register</NavLink>
      </form>
    </>
  )
}

export default Login

模型Schema文件代码

const validator = require('validator');
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");

const residentSchema = mongoose.Schema({
    blockNumber: {
        type: Number, 
        required: true
    },
    flatNumber: {
        type: Number, 
        required: true
    },
    name: {
        type: String, 
        required: true
    },
    password: {
        type: String, 
        required: true
    },
    email: {
        type: String,
        validate: {
            validator: validator.isEmail,
            message: "please enter a valid email address"
        }
    },
    phone : {
        type: Number,
        required: true
    },
    saptami: [],
    astami: [],
    nabami: [],
    dasami: [],
    tokens: [{
        token:{
            type: String,
            required: true
        }
    }]
});

residentSchema.methods.generateAuthToken = async function () {
    try {
        console.log(this._id);
        const token = jwt.sign({_id: this._id.toString()}, "thisisnotmyresumeprojectbutapassion", {
            expiresIn: 300
        });
        this.tokens = this.tokens.concat({token: token});
        await this.save();
        return token;
    } catch (error) {
        res.send(error);
        console.log(error);
    }
}

residentSchema.pre("save", async function (next){
    if(this.isModified("password")){
        // const passwordHash = await bcrypt.hash(password, 10);
        console.log(`the current password is ${this.password}`);
        this.password = await bcrypt.hash(this.password, 10);
    }
    
    next();
})

const Resident = new mongoose.model("Resident", residentSchema);

module.exports = Resident;

核心问题与修复方案

1. 前端路由无登录校验

当前/dashboard等敏感路由直接开放,没有做登录状态拦截。需要添加受保护路由组件,校验全局登录状态:

// App.js中新增受保护路由组件
const ProtectedRoute = ({ children }) => {
  return auth ? children : <Navigate to="/login" />;
};

// 替换原有敏感路由配置,例如:
<Route path='/dashboard' element={
  <ProtectedRoute>
    <Dashboard />
  </ProtectedRoute>
} />

2. 全局状态管理未启用

取消AuthApi.Provider的注释,让全局登录状态可以在路由和组件间共享:

// App.js中取消注释
<AuthApi.Provider value={{auth, setAuth}}>
  {/* 原有BrowserRouter代码 */}
</AuthApi.Provider>

3. 后端登录逻辑漏洞

  • 登录接口无论密码是否匹配,最终都会返回200状态码,且错误处理被注释,前端无法判断登录结果。
  • 最后一行res.status(200).send({ user, token: jwt.token });中user和jwt未定义,会导致服务器报错。

修复后的登录接口:

router.post("/login", async (req, res) => {
    try {
        const { blockNumber, flatNumber, password } = req.body;

        const resident = await Resident.findOne({ blockNumber, flatNumber });
        if (!resident) {
            return res.status(401).send("Invalid Login Details");
        }

        const isMatch = await bcrypt.compare(password, resident.password);
        if (!isMatch) {
            return res.status(401).send("Invalid Login Details");
        }

        const token = await resident.generateAuthToken();
        res.cookie("jwt", token, {
            httpOnly: true,
            expires: new Date(Date.now() + 300000)
        });

        res.status(200).send({ message: "Successful Login" });
    } catch (error) {
        console.log(error);
        res.status(500).send("Server Error");
    }
});

4. 前端Login组件逻辑错误

无论登录成功与否,都会跳转到/dashboard,完全失去校验意义。修复后仅在登录成功时跳转并更新全局状态:

// Login.jsx中引入上下文并修改逻辑
import { useContext } from 'react';
import AuthApi from '../utils/AuthApi';

const Login = () => {
  const { setAuth } = useContext(AuthApi);

  const loginButton = async (event) => {
    event.preventDefault();
    // ...获取表单值逻辑

    try {
      const response = await axios.post('http://localhost:8000/login', {
        blockNumber, flatNumber, password
      }, { withCredentials: true });

      if (response.status === 200) {
        alert("Login Successful");
        setAuth(true);
        navigate("/dashboard");
      }
    } catch (error) {
      alert("Invalid Login Details");
      // 登录失败不跳转,留在登录页
    }
  }
}

5. 确保auth中间件正确验证token

检查../middleware/auth.js是否正确解析cookie中的JWT并验证用户身份,示例实现:

const jwt = require('jsonwebtoken');
const Resident = require('../models/residentModels');

const auth = async (req, res, next) => {
  try {
    const token = req.cookies.jwt;
    if (!token) return res.status(401).send("Please login first");

    const decoded = jwt.verify(token, "thisisnotmyresumeprojectbutapassion");
    const resident = await Resident.findOne({
      _id: decoded._id,
      "tokens.token": token
    });

    if (!resident) throw new Error();

    req.resident = resident;
    next();
  } catch (error) {
    res.status(401).send("Authentication failed");
  }
}

module.exports = auth;

内容的提问来源于stack exchange,提问作者Shanai

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 17:11:11