英文:
JsonWebTokenError: jwt must be provided" when using JWT for authentication in Express.js
问题
I am building a web application using Express.js for the backend and React for the frontend. I am implementing user authentication using JSON Web Tokens (JWT). However, I am encountering an error
> JsonWebTokenError: jwt must be provided
[![enter image description here][1]][1]
when trying to verify the token on the server side.
I have the following setup on the server:
I am using jsonwebtoken library for generating and verifying JWT tokens.
I have a /login route that handles user login and issues a JWT token upon successful authentication.
I also have a /post route that allows authenticated users to create new posts.
Here is the relevant server-side code:
const express = require('express');
const cors = require('cors');
const mongoose = require("mongoose");
const User = require('./models/User');
const Post = require('./models/Post');
const bcrypt = require('bcryptjs');
const app = express();
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');
const multer = require('multer');
const uploadMiddleware = multer({ storage: multer.memoryStorage() });
const admin = require('firebase-admin');
require('dotenv').config();
const bodyParser = require('body-parser');
const serviceAccount = {
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\n/g, '\n'),
};
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
storageBucket: process.env.FIREBASE_BUCKET,
});
const bucket = admin.storage().bucket();
const salt = bcrypt.genSaltSync(10);
const secret = process.env.JWT_SECRET;
app.use(cors({
origin: 'https://personal-website-ea41b.web.app',
credentials: true,
optionsSuccessStatus: 200
}));
app.use(express.urlencoded({ extended: false }))
app.use(express.json({limit: '50mb'}));
app.use(cookieParser());
const uri = process.env.MONGO_URL;
mongoose.connect(uri, {
serverSelectionTimeoutMS: 30000,
});
app.post('/register', async (req, res) => {
const { username, password } = req.body;
try {
const userDoc = await User.create({
username,
password: bcrypt.hashSync(password, salt),
});
res.json(userDoc);
} catch (e) {
console.log(e);
res.status(400).json(e);
}
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const userDoc = await User.findOne({ username });
const passOk = bcrypt.compareSync(password, userDoc.password);
if (passOk) {
jwt.sign({ username, id: userDoc._id }, secret, {}, (err, token) => {
console.log('token-secret', secret, token)
if (err) throw err;
res.cookie('token', token).json({
id: userDoc._id,
username,
token,
});
});
} else {
res.status(400).json('wrong credentials');
}
});
app.get('/profile', (req, res) => {
const { token } = req.cookies;
console.log('profile token', token);
jwt.verify(token, secret, {}, (err, info) => {
if (err) throw err;
res.json(info);
});
});
app.post('/logout', (req, res) => {
res.cookie('token', '').json('ok');
});
app.post('/post', uploadMiddleware.single('file'), async (req, res) => {
const { originalname } = req.file;
const { title, summary, content } = req.body;
const fileUpload = bucket
.file(`blog_covers/` + originalname);
const blobStream = fileUpload.createWriteStream({
metadata: {
contentType: req.file.mimetype,
},
});
blobStream.on('error', (err) => {
console.error(err);
res.status(500).json('Error uploading file');
});
blobStream.on('finish', async () => {
const [url] = await fileUpload.getSignedUrl({
action: 'read',
expires: '03-01-2500',
});
const { token } = req.cookies;
console.log('token',token);
jwt.verify(token, secret, {}, async (err, info) => {
if (err) throw err;
const postDoc = await Post.create({
title,
summary,
content,
cover: url,
author: info.id,
});
res.json(postDoc);
});
});
blobStream.end(req.file.buffer);
});
app.put('/post', uploadMiddleware.single('file'), async (req, res) => {
const { id, title, summary, content } = req.body;
const postDoc = await Post.findById(id);
const isAuthor = JSON.stringify(postDoc.author) === JSON.stringify(info.id);
if (!isAuthor) {
return res.status(400).json('you are not the author');
}
const fileUpload = bucket.file(req.file.originalname);
const blobStream = fileUpload.createWriteStream({
metadata: {
contentType: req.file.mimetype,
},
});
blobStream.on('error', (err) => {
console.error(err);
res.status(500).json('Error uploading file');
});
blobStream.on('finish', async () => {
const [url] = await fileUpload.getSignedUrl({
action: 'read',
expires: '03-01-2500',
});
const { token } = req.cookies;
jwt.verify(token, secret, {}, async (err, decodedToken) => {
if (err) throw err;
await postDoc.update({
title,
summary,
content,
cover: url ? url : postDoc.cover,
});
res.json(postDoc);
});
});
blobStream.end(req.file.buffer);
});
app.get('/post', async (req, res) => {
res.json(
await Post.find()
.populate('author', ['username'])
.sort({ createdAt: -1 })
.limit(20)
);
});
app.get('/post/:id', async (req, res) => {
const { id } = req.params;
const postDoc = await Post.findById(id).populate('author', ['username']);
res.json(postDoc);
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
In the client-side code, I am sending the JWT token as a Bearer token in the Authorization header for the requests that require authentication.
Client-side code for login(React):
import { useContext, useState } from "react";
import { Navigate } from "react-router-dom";
import { UserContext } from "../UserContext";
import Cookies from "js-cookie";
export default function LoginPage() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [redirect, setRedirect] = useState(false);
const { setUserInfo } = useContext(UserContext);
async function login(ev) {
ev.preventDefault();
const
<details>
<summary>英文:</summary>
Description:
I am building a web application using Express.js for the backend and React for the frontend. I am implementing user authentication using JSON Web Tokens (JWT). However, I am encountering an error
> JsonWebTokenError: jwt must be provided
[![enter image description here][1]][1]
when trying to verify the token on the server side.
I have the following setup on the server:
I am using jsonwebtoken library for generating and verifying JWT tokens.
I have a /login route that handles user login and issues a JWT token upon successful authentication.
I also have a /post route that allows authenticated users to create new posts.
Here is the relevant server-side code:
const express = require('express');
const cors = require('cors');
const mongoose = require("mongoose");
const User = require('./models/User');
const Post = require('./models/Post');
const bcrypt = require('bcryptjs');
const app = express();
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');
const multer = require('multer');
const uploadMiddleware = multer({ storage: multer.memoryStorage() });
const admin = require('firebase-admin');
require('dotenv').config();
const bodyParser = require('body-parser');
const serviceAccount = {
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\n/g, '\n'),
};
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
storageBucket: process.env.FIREBASE_BUCKET,
});
const bucket = admin.storage().bucket();
const salt = bcrypt.genSaltSync(10);
const secret = process.env.JWT_SECRET;
app.use(cors({
origin: 'https://personal-website-ea41b.web.app',
credentials: true,
optionsSuccessStatus: 200
}));
app.use(express.urlencoded({ extended: false }))
app.use(express.json({limit: '50mb'}));
app.use(cookieParser());
const uri = process.env.MONGO_URL;
mongoose.connect(uri, {
serverSelectionTimeoutMS: 30000,
});
app.post('/register', async (req, res) => {
const { username, password } = req.body;
try {
const userDoc = await User.create({
username,
password: bcrypt.hashSync(password, salt),
});
res.json(userDoc);
} catch (e) {
console.log(e);
res.status(400).json(e);
}
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const userDoc = await User.findOne({ username });
const passOk = bcrypt.compareSync(password, userDoc.password);
if (passOk) {
jwt.sign({ username, id: userDoc._id }, secret, {}, (err, token) => {
console.log('token-secret', secret, token)
if (err) throw err;
res.cookie('token', token).json({
id: userDoc._id,
username,
token,
});
});
} else {
res.status(400).json('wrong credentials');
}
});
app.get('/profile', (req, res) => {
const { token } = req.cookies;
console.log('profile token', token);
jwt.verify(token, secret, {}, (err, info) => {
if (err) throw err;
res.json(info);
});
});
app.post('/logout', (req, res) => {
res.cookie('token', '').json('ok');
});
app.post('/post', uploadMiddleware.single('file'), async (req, res) => {
const { originalname } = req.file;
const { title, summary, content } = req.body;
const fileUpload = bucket
.file(blog_covers/
+ originalname);
const blobStream = fileUpload.createWriteStream({
metadata: {
contentType: req.file.mimetype,
},
});
blobStream.on('error', (err) => {
console.error(err);
res.status(500).json('Error uploading file');
});
blobStream.on('finish', async () => {
const
action: 'read',
expires: '03-01-2500',
});
const { token } = req.cookies;
console.log('token',token);
jwt.verify(token, secret, {}, async (err, info) => {
if (err) throw err;
const postDoc = await Post.create({
title,
summary,
content,
cover: url,
author: info.id,
});
res.json(postDoc);
});
});
blobStream.end(req.file.buffer);
});
app.put('/post', uploadMiddleware.single('file'), async (req, res) => {
const { id, title, summary, content } = req.body;
const postDoc = await Post.findById(id);
const isAuthor = JSON.stringify(postDoc.author) === JSON.stringify(info.id);
if (!isAuthor) {
return res.status(400).json('you are not the author');
}
const fileUpload = bucket.file(req.file.originalname);
const blobStream = fileUpload.createWriteStream({
metadata: {
contentType: req.file.mimetype,
},
});
blobStream.on('error', (err) => {
console.error(err);
res.status(500).json('Error uploading file');
});
blobStream.on('finish', async () => {
const
action: 'read',
expires: '03-01-2500',
});
const { token } = req.cookies;
jwt.verify(token, secret, {}, async (err, decodedToken) => {
if (err) throw err;
await postDoc.update({
title,
summary,
content,
cover: url ? url : postDoc.cover,
});
res.json(postDoc);
});
});
blobStream.end(req.file.buffer);
});
app.get('/post', async (req, res) => {
res.json(
await Post.find()
.populate('author', ['username'])
.sort({ createdAt: -1 })
.limit(20)
);
});
app.get('/post/:id', async (req, res) => {
const { id } = req.params;
const postDoc = await Post.findById(id).populate('author', ['username']);
res.json(postDoc);
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
In the client-side code, I am sending the JWT token as a Bearer token in the Authorization header for the requests that require authentication.
Client-side code for login(React):
import { useContext, useState } from "react";
import { Navigate } from "react-router-dom";
import { UserContext } from "../UserContext";
import Cookies from "js-cookie";
export default function LoginPage() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [redirect, setRedirect] = useState(false);
const { setUserInfo } = useContext(UserContext);
async function login(ev) {
ev.preventDefault();
const response = await fetch('https://personal-website-on6a.onrender.com/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
withCredentials: true,
});
if (response.ok) {
response.json().then(userInfo => {
setUserInfo(userInfo);
Cookies.set('token', userInfo.token, { expires: 7 });
setRedirect(true);
});
} else {
alert('wrong credentials');
}
}
if (redirect) {
return <Navigate to={'/'} />
}
return (
<form className="login" onSubmit={login}>
<h1>Login</h1>
<div className="form-container">
<input
type="text"
placeholder="username"
value={username}
onChange={ev => setUsername(ev.target.value)}
/>
<input
type="password"
placeholder="password"
value={password}
onChange={ev => setPassword(ev.target.value)}
/>
<div style={{ width: '400px' }}>
<button className="btn ac_btn">Login</button>
</div>
</div>
</form>
);
}
Client-side code for creating post(React):
import 'react-quill/dist/quill.snow.css';
import { useState } from "react";
import { Navigate } from "react-router-dom";
import Editor from "../Editor";
import Cookies from "js-cookie";
export default function CreatePost() {
const [title, setTitle] = useState('');
const [summary, setSummary] = useState('');
const [content, setContent] = useState('');
const [files, setFiles] = useState('');
const [redirect, setRedirect] = useState(false);
const userToken = Cookies.get('token');
const headers = new Headers({
'Authorization': Bearer ${userToken}
,
});
async function createNewPost(ev) {
const data = new FormData();
data.set('title', title);
data.set('summary', summary);
data.set('content', content);
data.set('file', files[0]);
ev.preventDefault();
const response = await fetch('https://personal-website-on6a.onrender.com/post', {
method: 'POST',
body: data,
headers: headers,
credentials: 'include',
withCredentials: true,
});
if (response.ok) {
setRedirect(true);
}
}
if (redirect) {
return <Navigate to={'/indexpage'} />
}
return (
<form onSubmit={createNewPost} style={{ padding: 20, alignItems: 'center', marginTop: "5%" }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: "2%" }}>
<input type="title" style={{ width: '75%', marginBottom: '10px' }}
placeholder={'Title'}
value={title}
onChange={ev => setTitle(ev.target.value)} />
<input type="summary" style={{ width: '75%', marginBottom: '10px' }}
placeholder={'Summary'}
value={summary}
onChange={ev => setSummary(ev.target.value)} />
<input type="file"
style={{ width: '75%', paddingBottom: '50px', marginBottom: '10px' }}
onChange={ev => setFiles(ev.target.files)} />
<div style={{ width: '75%', marginBottom: '10px' }}>
<Editor value={content} onChange={setContent} />
</div>
<div style={{ width: '75%' }}>
<button className="btn ac_btn" style={{ marginTop: '5px' }}>Create post</button>
</div>
</div>
</form>
);
}
Can someone please help me understand why I am getting this error and how to fix it? Is there something I am missing in the server-side code or in the way I am handling the JWT token on the client-side? Any insights or suggestions would be greatly appreciated. Thank you!
[1]: https://i.stack.imgur.com/EKg9j.png
</details>
# 答案1
**得分**: 1
将服务器端的代码更改为从标头中解析令牌。
在客户端,您将令牌发送到标头中:
```javascript
const headers = new Headers({
'Authorization': `Bearer ${userToken}`,
});
但在服务器端,您正在查找令牌在cookies中:
const { token } = req.cookies;
英文:
Change your server sided code to parse the token from the header.
On the client, you are sending the token in the header:
const headers = new Headers({
'Authorization': `Bearer ${userToken}`,
});
But on the server, you are looking for the token in the cookies:
const { token } = req.cookies;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论