8
votes

I am building a login system using express for node.js and react.js. In my back-end when a user logs in, it creates a cookie. When I go to Network > Login I can see this:

Set-Cookie: user_id=s%3A1.E%2FWVGXrIgyXaM4crLOoxO%2Fur0tdjeN6ldABcYOgpOPk; Path=/; HttpOnly; Secure

But when I go to Application > Cookies > http://localhost:3000, there is nothing there. I believe that is because I am not allowing credentials to go through correctly when I do a post request from the client side. How do I go about this? Please, let me know if I can improve my question in any way.

            //Login back-end
            router.post('/login', (req, res, next) => {
                if(validUser(req.body)) {
                    User
                        .getOneByEmail(req.body.email)
                        .then(user => {
                            if(user) {
                                bcrypt
                                    .compare(req.body.password_digest, user.password_digest)
                                    .then((result) => {
                                        if(result) {
                                            const isSecure = process.env.NODE_ENV != 'development';

                                            res.cookie('user_id', user.id, {
                                                httpOnly: true,
                                                secure: isSecure,
                                                signed: true
                                            })
                                            res.json({
                                                message: 'Logged in'
                                            });
                                        } else {
                                            next(new Error('Invalid Login'))
                                        }
                                    });
                            } else {
                                next(new Error('Invalid Login'))
                            }
                        });
                } else {
                    next(new Error('Invalid Login'))
                }
            });

            //Allow CORS index.js
            app.use(
            cors({
                origin: "http://localhost:3000",
                credentials: true
            })
            );

            //Login client side (React.js)
            loginUser(e, loginEmail, password) {
            e.preventDefault();

            let email = loginEmail;
            let password_digest = password;
            let body = JSON.stringify({ email, password_digest });

            fetch("http://localhost:5656/api/login", {
                method: "POST",
                headers: {
                "Content-Type": "application/json"
                },
                credentials: "include",
                body
            })
                .then(response => response.json())
                .then(user => {
                console.log(user);
                });
            }
4
Use react-cookie library, then you can use cookies.get('key' ) and cookies.set('key', 'value')Sameer Reza Khan
Thank you for your comment Sameer. However, I could not make it work. I am using universal cookie like this: import Cookies from 'universal-cookie'; const cookies = new Cookies(); cookies.set('myCat', 'Pacman', { path: '/' }); console.log(cookies.get('myCat')); // Pacman When I go to Application > Cookies > localhost:3000, I can see this 'myCat' cookie there. But I am not sure how to integrate that with my application. (I am trying to display the code correctly but I can't.)Windbox
All of your configuration seems correct except 1 thing that I'm not sure about. When you're setting the cookie in your login post route, make sure that the secure flag is false (only for development). In production, set that to true. This is because localhost is HTTP not HTTPS. So make sure your isSecure variable is false. That's the only thing that looks like a potential issue to me.Harshit Trehan

4 Answers

5
votes

You should be secure of set "credentials" in the server and in app.

Try to set on you index.js or app.js server side this:

  app.use(function(req, res, next) {
  res.header('Content-Type', 'application/json;charset=UTF-8')
  res.header('Access-Control-Allow-Credentials', true)
  res.header(
    'Access-Control-Allow-Headers',
    'Origin, X-Requested-With, Content-Type, Accept'
  )
  next()
})

and in you client site add options like this:

let axiosConfig = {
  withCredentials: true,
}

export async function loginUser(data) {
  try {
    const res = await axios.post(
      `${URL}:${PORT}/${API}/signin`,
      data,
      axiosConfig
    )
    return res
  } catch (error) {
    console.log(error)
  }
}

Edit

To set "credentials" in server we need this line:

res.header('Access-Control-Allow-Credentials', true)

This would let you handle credentials includes in headers.

You also have to tell to axios to set credentials in headers with:

withCredentials: true
3
votes

Do not forget to adjust cors middleware.

Your node.js express code

const express = require("express");
const cors = require('cors')

const app = express();

app.use(cors(
  {
    origin: 'http://localhost:3000',
    optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
  }
));

app.use(function(req, res, next) {
  res.header('Content-Type', 'application/json;charset=UTF-8')
  res.header('Access-Control-Allow-Credentials', true)
  res.header(
    'Access-Control-Allow-Headers',
    'Origin, X-Requested-With, Content-Type, Accept'
  )
  next()
})

app.get("/auth", function(req, res){

  res.cookie('token', 'someauthtoken')
  res.json({id: 2});
});

app.listen(3030);

Your front-end code

import React, { useEffect } from 'react';
import axios from 'axios';


async function loginUser() {
  try {
    const res = await axios.get(
      'http://localhost:3030/auth',
      {
        withCredentials: true,
      }
    )
    return res
  } catch (error) {
    console.log(error)
  }
}

function App() {

  useEffect(() => {
    loginUser();
  }, [])

  return (
    <div>

    </div>
  );
}

export default App;
0
votes

It is because you set httpOnly: true.

This will block the visibility to client side, like reading from javaScript document.cookie().

You can solve this by turn it off.

0
votes

If you can't see your cookie in the browser, I think it is because you're setting hhtpOnly to true in the cookie's options.

cookie.httpOnly Specifies the boolean value for the HttpOnly Set-Cookie attribute. When truthy, the HttpOnly attribute is set, otherwise it is not. By default, the HttpOnly attribute is set.

Note: be careful when setting this to true, as compliant clients will not allow client-side JavaScript to see the cookie in document.cookie

   res.cookie('user_id', user.id, {
                                            httpOnly: false, // try this
                                            secure: isSecure,
                                            signed: true
                                        })