react-nativejwttokenredux-toolkitasyncstorage

how to pass token in local storage (async storage) to the backend during api call


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.


Solution

  • 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
    }