Search code examples
reactjsreduxgraphqlapollo-client

Storing user data using jwt


I am fetching user data from the graphql backend through the apollo client. My goal is to keep the user logged in, so I signed jwt token with all user data, passed it to localStorage(only the token), decoded it, and passed all values to redux-store.

       UserModel:
 userSchema.methods.createJWT = function (payload) {
      return jwt.sign({ ...payload }, process.env.JWT_SECRET, {
        expiresIn: '1d',
      });
    };

userLogin:

      await user.save();
      return {
        ...user._doc,
        id: user._id,
        token: user.createJWT(user._doc),
}

reduxSlice

const userSlice = createSlice({
  name: 'user',
  initialState: {
    userInfo: localStorage.getItem('jwtToken')
      ? jwtDecode(localStorage.getItem('jwtToken'))
      : null,
  },  
  reducers: {
    loginUser: (state, action) => {
      localStorage.setItem('jwtToken', action.payload.token);
      state.userInfo = action.payload;
    },

I am wondering if this is ok that the token is holding too much info like:

{
  "_id": "62a9ee3878c4979fedb471c5",
  "username": "***",
  "email": "***",
  "password": "$2a$12$hN2lfCtEbqOOFSlHpapyfuxYAHdEGUYKeHY4BMK1YvYOtSG7zHwcS",
  "isAdmin": false,
  "shippingAddress": [],
  "createdAt": "2022-06-15T14:35:36.877Z",
  "updatedAt": "2022-06-16T09:04:59.367Z",
  "__v": 0,
  "firstName": "***",
  "lastName": "***",
  "shoeSize": 4,
  "iat": 1655371413,
  "exp": 1655457813
}

There is another effective way to save user data and keep him logged in?


Solution

  • it's not recommended (actually very dangerous) to return all information with the jwt token especially password. I think userId is enough! But you can also return username, firstName, lastName, etc. But in some cases even returning email address is not a good approach for some user privacy reasons.

    I mean by that you have only to get the userId once there is a user and the credentials are correct, then :

    const userToken = {
      userId: user._id,
      username: user.username,
    };
     
    return {
      user,
      token: user.createJWT(userData)
    };

    Now after signing the jwt token , you can set whatever data from user inside some redux state or even react context , (choose your favorite) , but DON'T set any password in the localStorage.

    Update: at the end you should store the user from the payload like this :

    state.userInfo = action.payload.user;

    Btw you should check the localStorage only to get the token and verify it , then based on userId you need to fetch the user and store it, here is an example :

      const getUser = React.useCallback(async (userId) => {
        try {
          const res = await axios.post('/auth/login', {userId}, {
            credentials: 'include',
            withCredentials: true
          });
          const { accessToken, user } = res.data;
          setState((currentState: IState) => ({
            ...currentState,
            user,
            loading: false,
            isAuth: !!accessToken,
            accessToken
          }));
        } catch (err) {
          setState((currentState: IState) => ({
            ...currentState,
            user: null,
            loading: false,
            isAuth: false,
            accessToken: ''
          }));
        }
      }, []);
    
      useEffect(() => {
        getUser(userId);
      }, [getUser]);
    
      useEffect(() => {
      const jwtToken = localStorage.getItem('jwtToken');
        if (jwtToken && jwt_decode(jwtToken)) {
          const { exp } = jwt_decode(jwtToken);
          const currentTime = Date.now() / 1000;
          if (exp < currentTime) {
            getUserById(userId);
          }
        }
      }, [getUser]);