0

What's the best practice handling user session when you get your token from HttpOnly cookies in react?

My login endpoint looks like this and as you can see token is set on cookies:

 @Post('login')
    @HttpCode(HttpStatus.OK)
    async login(@Ip() ipAddress, @Request() req, @Res() res: Response) {
      const auth = await this.basicAuthService.login(req.user, ipAddress);
      const cookieOptions = setTokenCookie();
      res.cookie('token', auth.token, { httpOnly: true });
      res.cookie('refreshToken', auth.refreshToken, { httpOnly: true });
      res.send(auth);
    }

And also I have another endpoint which decodes a token in order to get user Data

 @Get('user-data')
    async getTokenPayload(@Request() req) {
      if (!('token' in req.cookies)) {
        throw new HttpException('Token was not provided', HttpStatus.NOT_FOUND);
      }

      const { token } = req.cookies;
      return this.basicAuthService.getTokenPayload(token);
    }

On FrontEnd I'm using API Context from React like this, and as you can see I'm fetching data from the /user-data endpoint:

export const UserContext = createContext<UserContextState>(userContextValue);

export const UserProvider:FC<UserProviderProps> = ({ children }) => {
  const [user, setUser] = useState<User>(userInitialValue);

  useEffect(() => {
    const getData = async () => {
      const tokenDecoded = await getUserData();
      setUser(tokenDecoded.user);
    };

    getData();
  }, []);

  return (
    <UserContext.Provider value={{ user, setUser }}>
      { children }
    </UserContext.Provider>
  );
};

It's working ok, the problem is a request is made every time the browser refreshes in order to get the users data and set it on the react state. I'm not sure whether this is a good practice, since sometimes user is not authenticated and obviously that /user-data request returns an error. I don't want to store the token on localStorage or set HttpOnly as false. Is there a better way to do it?

lak
  • 454
  • 6
  • 21
  • When you say it's working is the cookie getting set in your React app and flagged as httpOnly? – Ntshembo Hlongwane Nov 09 '20 at 23:24
  • No, the cookie is set in the browser and flagged as httpOnly. In other to get data from that cookie I created a new endpoint on the backend to decode it, the cookie is sent in the request, the response is the user data and that response I set it in the React State. – lak Nov 09 '20 at 23:27
  • Are you using NodeJS for making this server side cookies? – Ntshembo Hlongwane Nov 09 '20 at 23:30
  • Yes, using nest.js as BE – lak Nov 10 '20 at 00:38

1 Answers1

0

From what I understand is your having server side session lets say for example express-session that which I know of and can explain but I believe that concept is the same with others.

  • So from what I understand is if when the user is logged in and a session is made that cookie is to be set in browser and will only be removed only if the expiration date has been met besides that then that cookie will stay there. Meaning that even on page reload that cookie will never go anywhere.

  • So I am to highly believe from what you saying that the cookie is not getting set in browser or maybe you just mis-explained, cause if the cookie is getting set and not yet expired even on page reload should be there

So if you are using NodeJS as your back-end below is an implementation on how you can handle express-session with react app and getting that cookie set in browser once user logged in and saving that session in mongodb the instance a session is made

Firstly you will need the following packages

npm i express-session connect-mongodb-session or yarn add express-session connect-mongodb-session

Now that we have packages that we need to setup our mongoStore and express-session middleware:

//Code in server.js/index.js (Depending on your server entry point)
import expressSession from "express-session";
import MongoDBStore from "connect-mongodb-session";
import cors from "cors";
const mongoStore = MongoDBStore(expressSession);

const store = new mongoStore({
  collection: "userSessions",
  uri: process.env.mongoURI,
  expires: 1000,
});
app.use(
  expressSession({
    name: "SESS_NAME",
    secret: "SESS_SECRET",
    store: store,
    saveUninitialized: false,
    resave: false,
    cookie: {
      sameSite: false,
      secure: process.env.NODE_ENV === "production",
      maxAge: 1000,
      httpOnly: true,
    },
  })
);

Now the session middleware is ready but now you have to setup cors to accept your ReactApp so to pass down the cookie and have it set in there by server

//Still you index.js/server.js (Server entry point)

app.use(
  cors({
    origin: "http://localhost:3000",
    methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
    credentials: true,
  })
);

Now our middlewares are all setup now lets look at your login route

router.post('/api/login', (req, res)=>{
    //Do all your logic and now below is how you would send down the cooki

    //Note that "user" is the retrieved user when you were validating in logic
    // So now you want to add user info to cookie so to validate in future
    const sessionUser = {
       id: user._id,
       username: user.username,
       email: user.email,
    };
    //Saving the info req session and this will automatically save in your     mongoDB as configured up in sever.js(Server entry point)
    request.session.user = sessionUser;

    //Now we send down the session cookie to client
    response.send(request.session.sessionID);

})

Now our server is ready but now we have to fix how we make request in client so that this flow can work 100%:

Code below: React App/ whatever fron-tend that your using where you handling logging in

//So you will have all your form logic and validation and below
//You will have a function that will send request to server 

const login = () => {
    const data = new FormData();
    data.append("username", username);
    data.append("password", password);

    axios.post("http://localhost:5000/api/user-login", data, {
      withCredentials: true, // Now this is was the missing piece in the client side 
    });
};

Now with all this you have now server sessions cookies as httpOnly

  • In my app cookies are set in the browser, they are HttpOnly, if I refresh they are still there. What I store in my cookie is a Jwt token which contains user information that I'll need in order to set it in my react state using the API context. Since my cookie is HttpOnly I cannot access to it on the front end, so right now I make a request to decode it and return the user data and then set it in the react state – lak Nov 10 '20 at 00:49
  • I think this is not a good practice, so I wondering what's the best way to persist user data (from a jwt stored in my cookies as HttpOnly) in my react state – lak Nov 10 '20 at 00:51
  • I think this is a duplicated from this, but it doesn't have an answer https://stackoverflow.com/questions/60482566/persisting-user-sessions-with-httponly-cookies-in-react – lak Nov 10 '20 at 01:46
  • You have to understand the core concept behind httpOnly cookies. Having such was to avoid client to access it using javaScript cause it raised security concerns so when using httpOnly cookies everything session related you leave it to your server to handle. Because if then client has access to that cookie then it defeats the purpose of having httpOnly cookie – Ntshembo Hlongwane Nov 10 '20 at 05:24
  • Yes, I understand. I was just wondering whether this is the best solution, as I mentioned it works. It just seems a little bit weird to me making a requests to get user data every browser refresh on frontend. – lak Nov 10 '20 at 15:36
  • As long as nothing is saved in local storage and only httpOnly cookie then what you doing is perfect man – Ntshembo Hlongwane Nov 10 '20 at 15:46
  • I hope my answer was hope. Goodluck with your projects mate – Ntshembo Hlongwane Nov 10 '20 at 16:38