JsonWebTokenError: 在 Express.js 中使用 JWT 进行身份验证时必须提供 jwt”

huangapple go评论60阅读模式
英文:

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 

&gt; 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

= await fileUpload.getSignedUrl({
action: 'read',
expires: '03-01-2500',
});

const { token } = req.cookies;
console.log(&#39;token&#39;,token);
jwt.verify(token, secret, {}, async (err, info) =&gt; {
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

= await fileUpload.getSignedUrl({
action: 'read',
expires: '03-01-2500',
});

const { token } = req.cookies;
jwt.verify(token, secret, {}, async (err, decodedToken) =&gt; {
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({
    &#39;Authorization&#39;: `Bearer ${userToken}`,
  });

But on the server, you are looking for the token in the cookies:

const { token } = req.cookies;

huangapple
  • 本文由 发表于 2023年7月23日 20:16:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76748185.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定