I am building a React App with authentication using JWTs. I am able to create a new user. I am able to log that user in. But I am unable to update that user's info in the MongoDb it is connected to. The error I am receiving is the following:
'Unable to verify token'
On the front end, the user's info page they are editing looks like the following:
UserInfoPage.js
import { useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import { useToken } from '../auth/useToken';
import { useUser } from '../auth/useUser';
export const UserInfoPage = () => {
const user = useUser();
const [token,setToken] = useToken();
const {id, email, info } = user;
// We'll use the history to navigate the user
// programmatically later on (we're not using it yet)
const history = useHistory();
// These states are bound to the values of the text inputs
// on the page (see JSX below).
const [favoriteFood, setFavoriteFood] = useState(info.favoriteFood || '');
const [hairColor, setHairColor] = useState(info.hairColor || '');
const [bio, setBio] = useState(info.bio || '');
// These state variables control whether or not we show
// the success and error message sections after making
// a network request (see JSX below).
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
const [showErrorMessage, setShowErrorMessage] = useState(false);
// This useEffect hook automatically hides the
// success and error messages after 3 seconds when they're shown.
// Just a little user interface improvement.
useEffect(() => {
if (showSuccessMessage || showErrorMessage) {
setTimeout(() => {
setShowSuccessMessage(false);
setShowErrorMessage(false);
}, 3000);
}
}, [showSuccessMessage, showErrorMessage]);
const saveChanges = async () => {
// Send a request to the server to
// update the user's info with any changes we've
// made to the text input values
//alert('Save functionality not implemented yet');
try{
const response = await axios.put(`/api/users/${id}`, {
favoriteFood,
hairColor,
bio,
}, {
headers: { Authorization: `Bearer ${token}`}
});
const { token: newToken } = response.data;
setToken(newToken);
setShowSuccessMessage(true);
}catch(error){
setShowErrorMessage(true);
console.log(error);
}
}
const logOut = () => {
// We'll want to log the user out here
// and send them to the "login page"
alert('Log out functionality not implemented yet');
}
const resetValues = () => {
// Reset the text input values to
// their starting values (the data we loaded from the server)
//alert('Reset functionality not implemented yet');
setFavoriteFood(info.favoriteFood);
setHairColor(info.hairColor);
setBio(info.bio);
}
// And here we have the JSX for our component. It's pretty straightforward
return (
<div className="content-container">
<h1>Info for {email}</h1>
{showSuccessMessage && <div className="success">Successfully saved user data!</div>}
{showErrorMessage && <div className="fail">Uh oh... something went wrong and we couldn't save changes</div>}
<label>
Favorite Food:
<input
onChange={e => setFavoriteFood(e.target.value)}
value={favoriteFood} />
</label>
<label>
Hair Color:
<input
onChange={e => setHairColor(e.target.value)}
value={hairColor} />
</label>
<label>
Bio:
<input
onChange={e => setBio(e.target.value)}
value={bio} />
</label>
<hr />
<button onClick={saveChanges}>Save Changes</button>
<button onClick={resetValues}>Reset Values</button>
<button onClick={logOut}>Log Out</button>
</div>
);
}
The backend route the request is being sent to:
updateUserInfoRoute.js
import jwt from 'jsonwebtoken';
import { ObjectID } from 'mongodb';
import { getDbConnection } from '../db';
export const updateUserInfoRoute = {
path: '/api/users/:userId',
method: 'put',
handler: async (req, res) => {
try{
const{ authorization } = req.headers;
const {userId} = req.params;
const updates = (
({
favoriteFood,
hairColor,
bio,
}) => ({
favoriteFood,
hairColor,
bio,
}))(req.body);
if (!authorization){
return res.status(401).json({message: 'No authorization headers sent'});
}
const token = authorization.split('')[1];
jwt.verify(token, process.env.JWT_SECRET, async (err, decoded ) => {
if (err) return res.status(401).json({message: 'Unable to verify token'});
console.log(err);
const { id } = decoded;
if (id !== userId) return res.status(403).json({message: 'No authorization to update user data' });
const db = getDbConnection('react-auth-db');
const result = await db.collection('users').findOneAndUpdate(
{ _id: ObjectID(id) },
{ $set: { info: updates }},
{ returnOriginal: false},
);
const {email, isVerified, info } = result.value;
jwt.sign({ id, email, isVerified, info }, process.env.JWT_SECRET, {expiresIn: '365d'}, (err, token) =>{
if(err){
return res.status(200).json(err);
}
res.status(200).json({ token });
})
})
}catch(error){
console.log("Mazzo the error is: " error);
}
},
}
What is the issue here? Thank you, Ironman
CodePudding user response:
You pass a token from frontend to backend with this format,
Bearer token
But on updateUserInfoRoute.js code, you split the token without space,
const token = authorization.split('')[1];
For this code, the token output will be
e
To resolve that, just add space on the split method,
const token = authorization.split(' ')[1];