Home > Blockchain >  Not able to set/receive cookies cross-domain using Netlify and Heroku
Not able to set/receive cookies cross-domain using Netlify and Heroku

Time:02-11

I have run into the issue where I cannot set the cookie in the browser, due to the client being hosted at Netlify and server at Heroku. It worked fine on localhost, so it seems it has something to do with it being cross-domain now.

Having read multiple posts on this it seems like it might have to do with cors or how I'm setting the cookies to begin with.

I read somewhere that it might help adding secure: true and sameSite: "none" when setting the cookie, so I added them but it didn't help. I also have tried adding domain into the cookie when setting it, but it also didn't help unfortunately.

I'm using Chrome and have changed the setting from blocking third party cookies with icognito to allow all cookies, as I have read that this might be an issue sometimes. However it did not yield any new results either.

I have also read that it might require a proxy for whatever reason or that it could be solved by hosting both client and server on Heroku. I would prefer not having to do any of these things if possible, but any ideas are welcome.

Server side with Node js (express) hosted on Heroku:

const express = require("express");
const app = express();
require("dotenv").config();
const cors = require("cors");
const mysql = require("mysql");
const jwt = require("jsonwebtoken");
const cookieParser = require("cookie-parser");

// port for heroku if needed
const PORT = 3001;

// app objects instantiated on creation of the express server
app.use(
  cors({
    origin: [
      "https://examples.netlify.app",
      "http://localhost:3000",
    ],
    methods: ["GET", "POST", "DELETE"],
    credentials: true,
  })
);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

/*
 * Handel user login
 */
app.post("/sign-in", (req, res) => {
  const { email, password } = req.body;

  sqlSelectAllUsers = "SELECT * FROM user_db WHERE email = ?";
  db.query(sqlSelectAllUsers, [email], (err, user) => {
    if (err) {
      res.send({ err: err });
    }

    if (user && user.length > 0) {
      // given the email check if the password is correct

      bcrypt.compare(password, user[0].password, (err, compareUser) => {
        if (compareUser) {
          //req.session.email = user;
          // create access token
          const accessToken = createAccessToken(user[0]);
          const refreshToken = createRefreshToken(user[0]);
          // create cookie and store it in users browser
          res.cookie("access-token", accessToken, {
            maxAge: 1000 * 60 * 30, // 30 min
            httpOnly: true, // hinder doing document.cookies as it will be httpOnly which will make it more safe
            secure: true,
            domain: "https://examples.netlify.app/",
            sameSite: "none",
          });
          res.cookie("refresh-token", refreshToken, {
            maxAge: 2.63e9, // approx 1 month
            httpOnly: true,
            secure: true,
            domain: "https://examples.netlify.app/",
            sameSite: "none",
          });

          // update refresh token in database
          const sqlUpdateToken =
            "UPDATE user_db SET refresh_token = ? WHERE email = ?";
          db.query(
            sqlUpdateToken,
            [refreshToken, user[0].email],
            (err, result) => {
              if (err) {
                res.send(err);
              }
              res.sendStatus(200);
            }
          );
        } else {
          res.send({ message: "Wrong email or password" });
        }
      });
    } else {
      res.send({ message: "Wrong email or password" });
    }
  });
});

app.listen(process.env.PORT || PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Client side with React hosted on Netlify:

  export default function SignIn() {
  const history = useHistory();

  const handleSubmit = (event) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    // eslint-disable-next-line no-console
    axios
      .post(
        "https://example.herokuapp.com/sign-in",
        {
          email: data.get("email"),
          password: data.get("password"),
        },
        { withCredentials: "true" }
      )
      .then((response) => {
        //check if good response then give user a token for valid login
        if (response.data === "OK") {
          history.push("/");
          history.go(0);
        }
      });
  };
}

CodePudding user response:

I ran into this issue a while ago and the short answer is: Heroku won't set cookies for the domain *.herokuapp.com, thus making it impossible to use Netlify or other hostings for your frontend if your app depends on cookies. This is because herokuapp.com is included in the Mozilla Foundation’s Public Suffix List.

There's nothing code-wise that you can do about this.

If you want to try something, the solution I found (working with an Express Server and an Angular frontend) was to add a static route/folder to the Express Server where I would put the files generated by building the Angular app (the ng build --configuration production command). When you get those files, you git add them and make the push with the Heroku CLI.

By doing so, Heroku builds the Express Server, and the files you built beforehand with the Angular CLI are all hosted in the Heroku dyno. That way, the cookie gets set because is no longer cross-domain and the app works.

More information about why Heroku prevents this in this link of their documentation: https://devcenter.heroku.com/articles/cookies-and-herokuapp-com

Edit: forgot to mention that my app used cookies because it implemented the "Local Strategy" of the Passport library. I didn't write any code to deal with cookies, but needed the hosting to be able to set them for my app to work.

CodePudding user response:

Okay so I finally solved it and thought I would write how I did it here.

Despite what seems to be the common belief, it is actually possible to send cookies between Heroku and Netlify, sort of..

I think that I added domain, sameSite and secure all at the same time when changing the way I set the cookies and did therefore not test the scenario without domain. After removing domain it was possible to set the cookies in heroku which then made it work in netlify. So it worked for login in, however there were 2 major issues with this. Cookies would randomly stop working and as soon as Heroku went into idle mode (which it seems to do on the free version) it would mess up the application and the cookies forcing me to restart all dynos. So even though it seemed possible to do it through Netlify and Heroku, I do NOT recommend using cookies cross domain. (I have also read that using sameSite:none may increase risk of CSRF attacks and is therefore not ideal to use when setting your cookies)

The final solution: I started hosting both client and server on heroku under the same url, using proxy etc. Here is link to the tutorial I followed, which worked great: https://www.youtube.com/watch?v=xgvLP3f2Y7k&list=LL&index=1&ab_channel=AvanTutor

Now the cookies works like they should and the website is up and running!

  • Related