8

I'm currently developing a MERN stack application and the authentication I use is JWT and saving it in my cookie. This is how I send the cookie after the user login.

              res
                .cookie("token", token, {
                  httpOnly: true,
                  secure: true,
                  sameSite: "none",
                })
                .send();

And I am logging in the user by getting the "token" cookie in my backend. However, I implemented Redux with this application and every time I refresh the page, it automatically logs out. What I want is to detect in my front-end(React) the "token" cookie in my browser and I can't get it. I've tried using npm js-cookie and still can't get it. Is there a way to get the "token" cookie? Or use redux-persist based on what I've read? Please help, thanks.

XypriL
  • 129
  • 1
  • 1
  • 9

5 Answers5

15

Like already explained by an other answer, you can't access httpOnly cookies via JS.

I personally would recommend you to use a diffrent approach. Sure, cookies and httpOnly sounds like a good Idea, and you may think that cookies are a thousand times better than localStorage, but at the end, it doesn't really matter if you store the token in localStorage or in a cookie. You could argue about cookies vs localStorage for hours, but both have their vulnerabilities (e.g.: cookies: CSRF-Attacks (https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html), localStorage: XSS).

Now, while you could theoretically use localStorage here, I am not advocating using it. I would recommand you to just ditch both cookies and localStorage and store the JWT in your app-state (be it with the context-api, redux etc.) and send the JWT with an authentication header with all the request you make from the front to backend. Of course your backend would then need to verify that token. You could, for example, just implement an authentication middleware that you add to all the routes that need authentication. Expiration is also really easy because you don't have to sync the expiration of the JWT and the cookie anymore. Just set the expiration on the JWT and the verification of that token in the auth middleware will catch that. If you want to know why this method is safe against CSRF-attacks, look here: Where to store JWT in browser? How to protect against CSRF?

Here are some good articles, I would really recommand you read a bit of the first one: https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/ https://medium.com/@ryanchenkie_40935/react-authentication-how-to-store-jwt-in-a-cookie-346519310e81

fabian
  • 268
  • 3
  • 15
7

You can't. "httpOnly" means "JavaScript cannot access it".

Using Redux-Persist would also not really help you determine if you are still logged in or if your session is timed out. That data could have been persisted weeks ago or the token could have been revoked.

The most sensible thing you can do it set up a /whoami endpoint on the server and just as a first action while your application initializes sending a request there. Either info about your user comes back -> great, save it and display it. Otherwise you get a "401 unauthorized" which means the user is not logged in and needs to log in.

phry
  • 35,762
  • 5
  • 67
  • 81
7

Although you cannot do anything with the httpOnly cookie in the frontend, there definitely IS a way to handle frontend-sent httpOnly cookies and extract your JWT from that cookie, all in the backend of your MERN stack app.

As far as persisting the user and preventing the 'logout upon refresh' issue, you will have to create a useEffect hook that constantly checks whether the token exists or not - we'll get to that later.

First, I recommend using Cors in your backend:

const cors = require("cors");

app.use(
  cors({
    origin: ["http://...firstOrigin...", ...],
    credentials: true,
  })
);

Once that's ready to go, set the following options when creating your httpOnly cookie. Also, create a non-httpOnly cookie that tracks your httpOnly cookie with same expiration date and a boolean value instead if the JWT. This will allow you to use the 'universal-cookie' library and actually read the non-httpOnly cookie in the frontend:

              res
                .cookie("token", token, {
                  origin: "http://...firstOrigin..."
                  expires: // set desired expiration here
                  httpOnly: true,
                  secure: true,
                  sameSite: "none",
                })
                .cookie("checkToken", true, {
                  origin: "http://...firstOrigin..."
                  expires: // same as above
                  secure: true,
                  sameSite: "none",
                })

Having created a 'checkToken' cookie that mimics our actual 'token', we can use it to set the state (useState hook) and persist the user if it exists and not expired, through the useEffect hook.

However, to send it correctly, we must specify a few things first. In this example, I will use axios to make such API call in the frontend.

Note that every API call's request header will contain our httpOnly cookie and it's content - we can confirm this by opening chrome dev tools' network tab, make the API call, then check the "Request Headers" for the "Cookie"...

const cookies = new Cookies();
const checkToken = cookies.get("checkToken");

const AuthUser = () => {
  const [user, setUser] = useState(checkToken);

  useEffect(() => {
    async function checkToken() {
      await axios
        .post("http://...yourBackend.../authToken", {
          withCredentials: true,    // IMPORTANT!!!
        })
        .then((res) => {
          // handle response - if successful, set the state...
          // to persist the user
        )}
        .catch((err) => {
          // handle error
        )}
    };
  
    checkToken();
  }, []);

  // Implement your login behavior here
}

Once that's done, we can confirm that we're getting such token in the request body of our API call in the backend (wherever that's handled), log the cookie in the console to check for it, then store the cookie's value in a variable to enable verification of said cookie:

app.post(".../authToken", (req, res) => {
  // Get all cookies from request headers
  const { cookie } = req.headers;

  // Check to see if we got our cookies
  console.log(cookie);   
  
  // Handle this as you please
  if (cookie == undefined) return;
  
  const token = cookie.split("token=")[1].split(";")[0];   // Yep, it's a string
  console.log(token);    // Check to see if we stored our cookie's JWT

  // Some middleware:

  jwt.verify(token, process.env.TOKEN, (err, user) => {
    // if success upon verification,
    // issue new 'token' and 'checkToken'
  });
});

Done.

Please note that this a general implementation and serves as only a guide to understanding the functionality of httpOnly cookies. OP never provided original code to go off of.

I hope this helps. Godspeed.

csarmiento
  • 71
  • 1
  • 2
4

We run into similar problem when improving the security of auth workflow on a project Created using React/Django

The question was: What is the best place to store JWT ?

After research we ended up implementing Oauth2 protocol, here is an article that helps you understand the logic if Refresh token rotation

Our implementation was

  1. Generate 2 tokens on backend side (Access Token [short life] and Refresh Token [long lifespan])
  2. Refresh token should be stored in HttpOnly cookie (as they mentioned in responses, it is not accessible on Client side by JS)
  3. At frontend level we use only Access Token, and when it is expired, we make a call to backend to regenerate another Access and Refresh
  4. Backend will access the Refresh Token in HttpOnly cookie and decide if it is valid to generate new Tokens
  5. If Backend generates new valid tokens, it sends Access Token to frontend and update Refresh Token in the Cookie

Ps: by this logic, you have no access to refresh token on frontend side, so when your Access Token no longer valid you tell the server to check Refresh Token stored in HttpOnly Cookie if it is still valid then regenerate other valid Tokens

I hope this inspires you

Malek
  • 81
  • 1
  • 5
0

You can use Node js and express to create the httponly cookie. To access a protected resource that requires Authorization header, you should create an endpoint in node that returns the token because httponly cookie can only be accessed from the server.

Then in react, use fetch api to call the endpoint, it returns to token.

cerberus
  • 331
  • 2
  • 9