2

I know there are already a lot of questions similar to this but I couldn't find a solution yet.
I managed to deploy my React app on a server, and cookies work fine. Cookie is set by the server with set-cookie and then automatically included in all subsequent requests
However, I cannot make it work when I launch it on localhost: the cookie is not included in the requests after the server has set it. I think I tried all possible solutions that I found online and any possible combinations of them.
The command I use is "start": "yarn run start"
Things I tried:

  • When the server sets the cookie, I tried to set it:
    1) both with and without secure option
    2) with domain set to null, false, '' and not set at all
    3) with and without http-only
  • I tried to use localhost:3000, 127.0.0.1:3000 and my.site.it.localhost:3000
  • I tried to set 127.0.0.1 my.site.it in the host file and connect to my.site.it:3000
  • I tried to follow this guide to use https in localhost and modify the launch command to
    "start": "set HTTPS=true&&set SSL_CRT_FILE=./cert.crt&&set SSL_KEY_FILE=./cert.key&&yarn run start"
    and also to
    "start": "set HOST=my.site.it&&set HTTPS=true&&set SSL_CRT_FILE=./cert.crt&&set SSL_KEY_FILE=./cert.key&&yarn run start"
  • I tried to set both credentials: 'same-origin' and credentials: 'include'
  • I tried to set withCredentials both to true and false

Is there something I am missing? Any other thing left to try?
Thank you in advance

Deffo
  • 191
  • 1
  • 8
  • 21

4 Answers4

0

I am listing out all the things you have to check. Do try it out once and this should work.

Server:

    app.use(cookieParser()); // To parse the incoming cookies
    const corsOptions = {
        credentials: true,
        origin: "http://localhost:3000" // Add your frontend origin here (Don't add '/' at the end)
    };
    app.use("*", cors(corsOptions)); // npm i cors

Also set your cookies using:

    await res.cookie("accessToken", token, {
        maxAge: ACCESS_TOKEN_EXPIRY * 1000,
        httpOnly: true
    });

Client (Use this for your API call):

   export default axios.create({
       baseURL: 'http://localhost:5000/',
       withCredentials: true,
   });

This is the exact set of options and this should work. Else, maybe add your code here to debug.

Satej Bidvai
  • 168
  • 2
  • 10
  • It didn't work. Also, I need something that works both on localhost and on the production server... – Deffo Sep 24 '22 at 10:43
0

Quote: ..."I tried to set 127.0.0.1 my.site.it in the host file and connect to my.site.it:3000"... TWO points about certificates,

  1. The key pair that was generated require a verifier Certificate Authority for their lookup as "registered" and "valid". Fat chance of having a CA registry on a localhost(unless you want to build a registry system yourself).
  2. Even though that can be byepassed the client software will issue a statement dialogue that the certificate does not appear to be valid though the client receiver can be set to auto accept.

Moreover, with regard to the information you gave that I quoted at top, you cannot change ports or site name and then use the same certificate! You must re-generate the certificate for ANY change of use parameters.

Interestingly, most countries ban private use of cryption because their government spies would have too much trouble penetrating that security. Most localhost (just server software writers) server writers are aware of that problem. It may be possible to bypass such law by being connected to internet with a valid in date registered certificate for the internal machine site, however nothing I ever have done

Cookies are a complex little string and it is only fair to say that the only way to "eventually" understand them is to read the IETF RFC 6265 "HTTP State Management Mechanism", there is no substitute, however, MSDN does have some good information. Note: Browsers do differ slightly in their treatment and rejection of cookies.

There are "clauses at using some cookie attributes with each other" particularly the __Host- Prefix that obliges the compressed attribute "Secure" and path=/ to be included, other attributes can cause the cookie to be illegal and rejected by the browser.

Consensus among many web programmers is localhost cannot set https/ssl secure cookies, however, reading the following parts of the RFC 6265 should create a cookie that will be accepted over https. But i must warn that if in use of a localhost environment to set an https cookie, there is no sufficient consensus whether that would work because the activity is entirely software brand and version dependent.

4.1.2.5. The Secure Attribute (compressed attribute)

4.1.2.6. The HttpOnly Attribute (compressed attribute)

4.1.2.7. The SameSite Attribute

4.1.3.1. The "__Secure-" Prefix

4.1.3.2. The "__Host-" Prefix

If you use chrome browser you can right click the page the cookie should come with, click "inspect" on the menu , pull open the new frame, locate a tab crossbar menu and the double >> subtle menu button, select "application" , then "storage" and scroll to cookies. tip: Always print your cookie string as a debug to output somewhere obtained from the point immediately before its send and before attempting to use it.

And finally, don't forget to check your test browsers cookie settings (various reasons), they may been changed sometime, somehow, but if anyone knew how these worked they'd probably lock them out too.

Samuel Marchant
  • 331
  • 2
  • 6
0

I've read through your question a few times and I'm not 'quite' sure what your goal is, but functionally creating cookies on a react app is fairly easy.

For example, I built an app that stores an authentication token in a cookie. I'll walk you through it.

I built a custom magic link auth, so I'll assume the user has clicked on a special link in their email which leads them to my /login/check page.

const CheckPage = () => {
    const router = useRouter();
    const { access_key, email } = router.query;

    const { data, mutate } = useLogin();
    const { data: me, isLoading } = useMe();

    useEffect(() => {
        if (access_key && email) {
            // check access key
            mutate({ accessKey: access_key, email })
        }
    }, [access_key, email]);

    useEffect(() => {
        if (data?.data) createCookie(data.data.token)
    }, [data])

    return (
        <PageContainer>
            {/** show some stuff to the user*/}
            
        </PageContainer>
    )
}

Here, I grab the access_key and send that to my backend (using useLogin()) to make sure it is valid. If it is, it gets returned and stored in the data variable.

Now, I have a useEffect function that, if the data variable exists, will create a cookie using a function I created called createCookie.

That function looks like this...

export const createCookie = (accessToken) => {
  document.cookie = `${process.env.NEXT_PUBLIC_ENVIRONMENT}_auth_access_token=${accessToken}; path=/; domain=${location.hostname}`
}

Here, I'm setting a cookie that is specific to the environment that I'm on. If I'm on a test environment, the cookie stored would like like this test_auth_access_token and have a value of the accessToken passed in. I am also setting some additional parameters like path=/ which makes this cookie available on every page on the website, and domain=${location.hostname} which specifies the domain at which this cookie is available (I believe this will actually get set automatically if not included).

Now, this cookie is available whenever I need to grab it (like for checking if a user has an active session, thus avoiding prompting them to login again).

If we want to grab that cookie and check its contents, we have another function that does that called getCookie.

function getCookie(cname) {
    let name = cname + "=";
    let decodedCookie = decodeURIComponent(document.cookie);
    let ca = decodedCookie.split(';');

    for(let i = 0; i <ca.length; i++) {
      let c = ca[i];
      while (c.charAt(0) == ' ') {
        c = c.substring(1);
      }
      if (c.indexOf(name) == 0) {
        return c.substring(name.length, c.length);
      }
    }
    return "";
}

which we invoke by doing something like...

const token = getCookie(`${process.env.NEXT_PUBLIC_ENVIRONMENT}_auth_access_token`)

You will know if your cookie is properly being stored by opening the inspect panel, opening the application tab, expanding the cookies option, and selecting localhost.

enter image description here

enter image description here

If the cookie is nonexistent or the value is incorrect, then the cookie is not being set properly and there is some other issue.

Apologies for the long post, I hope this is helpful.

Kyle Pendergast
  • 739
  • 1
  • 8
  • 17
0

This is how you set the cookie in express

exports.postLogin = (req, res, next) => {
  // in chrome dev tools/applications click on "Cookies" side tab
  // you will see name:LoogedIn and value:true
  //in dev-tools, click on one of requests and "Headers" tab you will see "Cookie" is set
  res.setHeader("Set-Cookie", "loggedIn=true Secure Max-age=10 ");
};

server sends the cookie and browser stores is in the application tab. next time our browser makes another request, it automatically attaches that cooki to the req obj.

req.get('Cookie')

set the cors. Since some browsers do not allow a server that responds with a wildcard(*) origin header to set cookies, explicitly set the url

app.use (
  cors ( {
    origin : "https://herokuapp.com " ,
    credentials : true ,
  } )

Since you are both on dev and prod you might set origin as the environment variable. Because while you are on dev, your origin might be set to prod.

Another issue might be, if you are using fetch api, cookies are not set by default. so you need to add credentials:true property. this example from mdn

const response = await fetch(url, {
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, *cors, same-origin
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, *same-origin, omit
    headers: {
      'Content-Type': 'application/json'
      // 'Content-Type': 'application/x-www-form-urlencoded',
    },
    redirect: 'follow', // manual, *follow, error
    referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
    body: JSON.stringify(data) // body data type must match "Content-Type" header
  });
Yilmaz
  • 35,338
  • 10
  • 157
  • 202