Hello everyone im working on a mern app and got curious how to implement the jwt token authentication, it works fine but i got a small problem in my front, the token set successfully on the localstorage after the user login, but when i try to fetch the values using axios while sending the token in the headers, the token value is always null on the first render, i had to refresh the page so the headers get access to the token from the local storage This is the useLogin component, the token is immediately set on the localStorage
const login = async (email, password) => {
const user = { email, password };
setError(null);
try {
const response = await axios.post(
"http://localhost:4000/api/user/login",
user
);
const result = response.data;
console.log(response);
if (response.statusText === "OK") {
setError(null);
setIsLoading(false);
// save the token in local storage
localStorage.setItem("user", JSON.stringify(result));
// update the dispatch
dispatch({ type: "LOGIN", payload: result });
}
} catch (error) {
const response = error.response.data;
setError(response.message);
//console.log(error);
}
};
This is my authContext where im trying to get the localStorage Value
export const AuthContext = createContext();
export const authReducer = (state, action) => {
switch (action.type) {
case "LOGIN":
return { user: action.payload.user };
case "LOGOUT":
return { user: null };
default:
return state;
}
};
export const AuthContextProvider = ({ children }) => {
const initialState = JSON.parse(localStorage.getItem("user")) || null;
const [state, dispatch] = useReducer(authReducer, { user: initialState });
// useEffect(() => {
// //check if there is a token in local storage
// const user = JSON.parse(localStorage.getItem("user"));
// console.log(user);
// if (user !== undefined && user !== null) {
// dispatch({ type: "LOGIN", payload: { user } });
// }
// }, []);
console.log("authContext state", state);
return (
<AuthContext.Provider value={{ ...state, dispatch }}>
{children}
</AuthContext.Provider>
);
};
i tried using useEffect and it resulted in the same problem, the getItem("user") always null on the fist render, and works fine after i refresh the page
finally the home component where im trying to fetch the data
the user.token variable always undefined on the first render but works fine when i refresh
import axios from "axios";
import { useWorkoutContext } from "../hooks/useWorkoutContext";
import { useAuthContext } from "../hooks/useAuthContext";
// component
import WorkoutDetails from "../components/workoutDetails";
import WorkoutForm from "../components/workoutForm";
const Home = () => {
// const [workouts, setWorkouts] = useState([]);
const { workouts, dispatch } = useWorkoutContext();
const [error, setError] = useState(null);
const { user } = useAuthContext();
useEffect(() => {
const fetchWorkouts = async () => {
console.log(user.token);
try {
if (user && user.token) {
const response = await axios.get(
"http://localhost:4000/api/workouts",
{
headers: {
Authorization: `Bearer ${user.token}`,
},
}
);
const result = response.data;
if (response.status === 200) {
// console.log(result);
// setWorkouts(result);
dispatch({ type: "SET_WORKOUTS", payload: result });
}
}
} catch (error) {
console.error("Error fetching workouts:", error);
setError("Error fetching workouts. Please check your network.");
}
};
//console.log(user);
if (user) {
fetchWorkouts();
}
}, [dispatch, user]);
return (
<div className="home">
<div className="workouts">
{workouts &&
workouts.map((workout) => {
return <WorkoutDetails key={workout._id} workout={workout} />;
})}
{error && <p>{error}</p>}
</div>
<WorkoutForm />
</div>
);
};
export default Home;
This is my Back requireAuth express code
import { userModel } from "../models/userModel.js";
const requireAuth = async (req, res, next) => {
// verify authentication
// we get authorization from the headers
const { authorization } = req.headers;
console.log(authorization);
if (!authorization) {
res.status(401).json({ error: "authorization required" });
}
// authorization is a string that contains the token
let token;
if (authorization) {
token = authorization.split(" ")[1];
console.log("Token is " + token);
}
try {
// returns the id of the user that is in the token
const { _id } = jwt.verify(token, process.env.SECRET);
console.log("id is " + _id);
req.user = await userModel.findOne({ _id }).select("_id");
next();
} catch (error) {
console.log(error);
res.status(401).json({ error: "request is not authorized" });
}
};
export default requireAuth;
im stuck on this for 2 days now :'((
That is happening because your response consists of email
, token
and user
object. You are storing the whole response in the local storage so you store an object consisting of email
, token
and user
. But when you handle the LOGIN
event you store just the user
object from the response there and the other two keys (email
and token
are discarded).
On the first render after the LOGIN event is dispatched you try to access the token
key but it is not in the user object - it was outside of it and you haven't saved it in the reducer.
When you refresh the page you parse the content from the local storage and in the initial state you have the whole response including the email
, token
and user
object. And when you use the parsed object and ask for the token
value it is now accessible.
You need to do the following change:
export const authReducer = (state, action) => {
switch (action.type) {
case "LOGIN":
return { user: action.payload}; // save the whole payload instead of just the user object (action.payload.user)
case "LOGOUT":
return { user: null };
default:
return state;
}
};