the createApi for my project is as follows:
const baseQuery = fetchBaseQuery({
baseUrl: "http://localhost:8000",
});
export const apiSlice = createApi({
baseQuery,
tagTypes: ["User"],
endpoints: (builder) => ({}),
});
I inject the updateUser endpoint using apiSlice.injectEndpoints(). the update user endpoint is as follows:
updateUser: builder.mutation({
query: ({ data, token }) => ({
url: `${USERS_URL}/profile`,
method: "PUT",
body: data,
headers: { Authorization: `Bearer ${token}` },
}),
}),
Now, in the backend I generate a token when user registers or logins. when the user (say logs in) the response consists of a user credentials and token. the code for login is:
const authUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user && (await user.matchPassword(password))) { // matchPw is a method in user model
const token = generateToken(user._id);
res.status(201).json({
_id: user._id,
name: user.name,
email: user.email,
token,
});
} else {}});
after successful login, the action is dispatched with the response of login which is saved in async storage as:
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
setCredentials: (state, action) => {
state.userInfo = action.payload;
AsyncStorage.setItem("userInfo", JSON.stringify(action.payload))
.then(() => console.log("User successfully saved"))
.catch((err) => console.error("Error saving user: ", err));
console.log(state.userInfo)
},
logout: (state) => {
state.userInfo = null
AsyncStorage.removeItem("userInfo")
console.log(state)
}
},
});
now, in my reactnative component (updateUserProfile.jsx), I get the userInfo as:
const userInfo = useSelector((state) => state.auth.userInfo);
const token = userInfo.token;
console.log(token)
this successfully logs out the token which was created during login.
now when the fields in updateUserProfile.jsx are edited and submitted for updation as:
const handleSubmit = async () => {
if (password != confirmPassword) {
alert("Passwords donot match");
} else {
try {
console.log("Frontend token: ", token);
const res = await updateUser({
data: {
_id: userInfo._id,
name,
email,
password,
},
token,
}).unwrap();
dispatch(setCredentials({ ...res })); // update the credentials as well
alert("Profile updated");
} catch (err) {
alert(err?.data?.message || err.error);
}
}
};
Here the line console.log("Frontend token: ", token);
also successfully logs out the token.
Now the endpoint that will be hit is /api/users/profile
. This route is a protected route as it requires user to be authenticated i.e user must have a token inorder to update their profile. The code for the route to be protected are as follows:
import jwt from "jsonwebtoken";
import asyncHandler from "express-async-handler";
import User from "../models/userModel.js";
const protect = asyncHandler(async (req, res, next) => {
let token = req.headers.authorization;
console.log("token: ", token);
if (token && token.startsWith("Bearer ")) {
token = token.split(" ")[1];
//if there is a token, we need to verify it
try {
//decode the token
const decoded = jwt.verify(token, process.env.JWT_SECRET); //decoded is a object, that now has a userId field
req.user = await User.findById(decoded.userId).select("-password"); //find the user with the id in the decoded object, as we don't want to return the password we do -password
// doing req.user allows us to access the current user in the protected routes by simply using req.user
next();
} catch (error) {
res.status(401);
throw new Error("Not authorized, invalid token!");
}
} else {
res.status(401);
throw new Error("Not authorized, no token!");
}
});
export { protect };
in the line console.log("token: ", token);
the log that I receive is token: Bearer undefined i.e the query where I passed headers: { Authorization: `Bearer ${token}` }
the "Bearer" has been detected however "token" remain undefined.
Query function accepts only a single arg.
export type query = <QueryArg>( arg: QueryArg, ) => string | Record<string, unknown>
Update your updateUser
endpoint to consume a single arg object with the properties you need to make the query.
updateUser: builder.mutation({
query: ({ data, token }) => ({
url: `${USERS_URL}/profile`,
method: "PUT",
body: data,
headers: { Authorization: `Bearer ${token}` },
}),
}),
Update the dispatched trigger function to pass a single argument. Be sure to also actually unwrap the resolved query result.
try {
const res = await updateUser({
data: {
_id: userInfo._id,
name,
email,
password,
},
token
}).unwrap();
dispatch(setCredentials({ ...res }));
} catch(error) {
// handle/ignore errors
}