試圖了解如何在客戶端中獲取然后保存用戶(在 Http only cookie 中使用 JWT 令牌),以便我可以進行條件渲染。我遇到的困難是如何連續知道用戶是否登錄,而不必在用戶每次更改/重繪 頁面時向服務器發送請求。(注意:問題不在于如何在 Http only cookie 中獲取令牌,我知道這是通過 完成的withCredentials: true)
所以我的問題是如何獲取/存盤訪問令牌,以便每次用戶在網站上執行某些操作時客戶端都不必向服務器發出請求。例如,導航欄應該根據用戶是否登錄進行條件渲染,然后我不想做“詢問服務器用戶是否有訪問令牌,然后如果沒有檢查用戶是否有重繪 令牌,那么每次用戶切換頁面時,如果為真,則回傳一個新的訪問令牌,否則重定向到登錄頁面。
客戶:
用戶背景關系.js
import { createContext } from "react";
export const UserContext = createContext(null);
應用程式.js
const App = () => {
const [context, setContext] = useState(null);
return (
<div className="App">
<BrowserRouter>
<UserContext.Provider value={{ context, setContext }}>
<Navbar />
<Route path="/" exact component={LandingPage} />
<Route path="/sign-in" exact component={SignIn} />
<Route path="/sign-up" exact component={SignUp} />
<Route path="/profile" exact component={Profile} />
</UserContext.Provider>
</BrowserRouter>
</div>
);
};
export default App;
組態檔.js
import { GetUser } from "../api/AuthenticateUser";
const Profile = () => {
const { context, setContext } = useContext(UserContext);
return (
<div>
{context}
<button onClick={() => GetUser()}>Change context</button>
</div>
);
};
export default Profile;
驗證用戶.js
import axios from "axios";
export const GetUser = () => {
try {
axios
.get("http://localhost:4000/get-user", {
withCredentials: true,
})
.then((response) => {
console.log(response);
});
} catch (e) {
console.log(`Axios request failed: ${e}`);
}
};
服務器:
驗證用戶.js
const express = require("express");
const app = express();
require("dotenv").config();
const cors = require("cors");
const mysql = require("mysql");
const jwt = require("jsonwebtoken");
const cookieParser = require("cookie-parser");
// hashing algorithm
const bcrypt = require("bcrypt");
const salt = 10;
// app objects instantiated on creation of the express server
app.use(
cors({
origin: ["http://localhost:3000"],
methods: ["GET", "POST"],
credentials: true,
})
);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
const db = mysql.createPool({
host: "localhost",
user: "root",
password: "password",
database: "mysql_db",
});
//create access token
const createAccessToken = (user) => {
// create new JWT access token
const accessToken = jwt.sign(
{ id: user.id, email: user.email },
process.env.ACCESS_TOKEN_SECRET,
{
expiresIn: "1h",
}
);
return accessToken;
};
//create refresh token
const createRefreshToken = (user) => {
// create new JWT access token
const refreshToken = jwt.sign(
{ id: user.id, email: user.email },
process.env.REFRESH_TOKEN_SECRET,
{
expiresIn: "1m",
}
);
return refreshToken;
};
// verify if user has a valid token, when user wants to access resources
const authenticateAccessToken = (req, res, next) => {
//check if user has access token
const accessToken = req.cookies["access-token"];
// if access token does not exist
if (!accessToken) {
return res.sendStatus(401);
}
// check if access token is valid
// use verify function to check if token is valid
jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
return next();
});
};
app.post("/token", (req, res) => {
const refreshToken = req.cookies["refresh-token"];
// check if refresh token exist
if (!refreshToken) return res.sendStatus(401);
// verify refresh token
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(401);
// check for refresh token in database and identify potential user
sqlFindUser = "SELECT * FROM user_db WHERE refresh_token = ?";
db.query(sqlFindUser, [refreshToken], (err, user) => {
// if no user found
if (user.length === 0) return res.sendStatus(401);
const accessToken = createAccessToken(user[0]);
res.cookie("access-token", accessToken, {
maxAge: 10000*60, //1h
httpOnly: true,
});
res.send(user[0]);
});
});
});
/**
* Log out functionality which deletes all cookies containing tokens and deletes refresh token from database
*/
app.delete("/logout", (req, res) => {
const refreshToken = req.cookies["refresh-token"];
// delete refresh token from database
const sqlRemoveRefreshToken =
"UPDATE user_db SET refresh_token = NULL WHERE refresh_token = ?";
db.query(sqlRemoveRefreshToken, [refreshToken], (err, result) => {
if (err) return res.sendStatus(401);
// delete all cookies
res.clearCookie("access-token");
res.clearCookie("refresh-token");
res.end();
});
});
// handle user sign up
app.post("/sign-up", (req, res) => {
//request information from frontend
const { first_name, last_name, email, password } = req.body;
// hash using bcrypt
bcrypt.hash(password, salt, (err, hash) => {
if (err) {
res.send({ err: err });
}
// insert into backend with hashed password
const sqlInsert =
"INSERT INTO user_db (first_name, last_name, email, password) VALUES (?,?,?,?)";
db.query(sqlInsert, [first_name, last_name, email, hash], (err, result) => {
res.send(err);
});
});
});
/*
* Handel user login
*/
app.post("/sign-in", (req, res) => {
const { email, password } = req.body;
sqlSelectAllUsers = "SELECT * FROM user_db WHERE email = ?";
db.query(sqlSelectAllUsers, [email], (err, user) => {
if (err) {
res.send({ err: err });
}
if (user && user.length > 0) {
// given the email check if the password is correct
bcrypt.compare(password, user[0].password, (err, compareUser) => {
if (compareUser) {
//req.session.email = user;
// create access token
const accessToken = createAccessToken(user[0]);
const refreshToken = createRefreshToken(user[0]);
// create cookie and store it in users browser
res.cookie("access-token", accessToken, {
maxAge: 10000*60, //1h
httpOnly: true,
});
res.cookie("refresh-token", refreshToken, {
maxAge: 2.63e9, // approx 1 month
httpOnly: true,
});
// update refresh token in database
const sqlUpdateToken =
"UPDATE user_db SET refresh_token = ? WHERE email = ?";
db.query(
sqlUpdateToken,
[refreshToken, user[0].email],
(err, result) => {
if (err) {
res.send(err);
}
res.sendStatus(200);
}
);
} else {
res.send({ message: "Wrong email or password" });
}
});
} else {
res.send({ message: "Wrong email or password" });
}
});
});
app.get("/get-user", (req, res) => {
const accessToken = req.cookies["acceess-token"];
const refreshToken = req.cookies["refresh-token"];
//if (!accessToken && !refreshToken) res.sendStatus(401);
// get user from database using refresh token
// check for refresh token in database and identify potential user
sqlFindUser = "SELECT * FROM user_db WHERE refresh_token = ?";
db.query(sqlFindUser, [refreshToken], (err, user) => {
console.log(user);
return res.json(user);
});
});
app.listen(4000, () => {
console.log("running on port 4000");
});
I began experimenting with useContext as you can see in the client code above. My initial idea was to use useEffect in the App component where I make a call to the function GetUser() which makes a request to "/get-user" which will user the refreshToken to find the user (don't know if it is bad practice to use refreshToken to find user in db, maybe I should store access token in db as well and use it to find user in db instead?) and then save things like id, first name, last name and email so that it may be displayed in the navbar or any other component if necessary.
However, I don't know if this is the right thing to do as I have heard a lot about using localStorge, memory or sessionStorage is better for keeping the JWT access token in, while you should keep the refresh token in the server and save it in the mySQL database I have created, only to be used once the user has lost their access token. How should I get access to my access token and how do I keep track of the user logged in? Do I really need to do a request to the server each time the user switches page or refresh page?
另外,我有一個關于何時應該在服務器中呼叫“/token”來創建新訪問令牌的問題。我是否應該總是嘗試使用訪問令牌來做需要身份驗證的事情,例如,如果它null在某個時候回傳,那么我向“/token”發出請求,然后重復用戶嘗試做的事情?
uj5u.com熱心網友回復:
每次用戶切換頁面或重繪 頁面時,我真的需要向服務器發出請求嗎?
那是最安全的方式。如果您想與 SPA 的當前安全最佳實踐保持一致,那么使用僅限 http 的、安全的、同站點的 cookie 是最佳選擇。重繪 不會經常發生在您的頁面上,因此應該不會有問題。
我最初的想法是在 App 組件中使用 useEffect ,在那里我呼叫函式 GetUser() 向“/get-user”發出請求,它將使用 refreshToken 來查找用戶
我要做的是首先驗證訪問令牌,如果它有效,然后從訪問令牌中取出 userId(如果您沒有它,您可以在手動創建令牌時輕松添加它)并閱讀來自資料庫的用戶資料。如果訪問令牌無效,則向網站回傳錯誤并讓用戶使用重繪 令牌獲取新的訪問令牌。所以我不會在這里混合職責 - 我不會使用重繪 令牌來獲取有關登錄用戶的資訊。
另外,我有一個關于何時應該在服務器中呼叫“/token”來創建新訪問令牌的問題。我是否應該始終嘗試使用訪問令牌來執行需要身份驗證的操作,例如,如果它在某個時候回傳 null,那么我向“/token”發出請求,然后重復用戶嘗試執行的操作?
是的,這就是它通常的實作方式。您使用訪問令牌呼叫受保護的端點。如果令牌已過期或無效,端點最好回傳 401 回應。然后您的應用程式知道它應該使用重繪 令牌來獲取新的訪問令牌。獲得新的訪問令牌后,您可以嘗試再次呼叫受保護的端點。如果您沒有設法獲得新的訪問令牌(例如,因為重繪 令牌已過期),則您要求用戶再次登錄。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/357633.html
