Search code examples
javascriptexpresscookiescross-domaincontent-security-policy

Reading cookies from a different domain with CSP


I am setting a secure, http only, same site none cookie on www.domain-a.org express server. Using CORS and CSP I want to fetch that cookie from www.domain-b.com which also uses express. Every time a user lands on domain-b, it will make a fetch request on the server to domain-a to get its cookie.

This is my example code below for testing purposes but it's not working. When I try to fetch domain-a cookie from domain-b I get back an empty object. Are these requests possible?

domain-a.org

With CORS I've set up these policies on my send-cookie route so that domain-b is only allowed to call the endpoint and request the cookie from domain-a:

Access-Control-Allow-Origin: 'https://www.domain-b.com'
Access-Control-Allow-Methods: ['GET','OPTIONS']
Access-Control-Allow-Origin: true
router.get('/set-cookie', (req, res) => {
  res.cookie('domain-a-cookie', 'domain-a-value', {
    httpOnly: true,
    sameSite: 'none',
    secure: true,
    maxAge: 60 * 60 * 24 * 30
  })

  res.send('set domain-a cookie')
})

router.get('/send-cookie', cors({ origin: 'https://www.domain-b.com', methods: ['GET','OPTIONS'], credentials: true, preflightContinue: true}), (req, res) => {
  const response = {
    status: 'success'
  }

  if (req.cookies && req.cookies['domain-a-cookie']) {
    response.data = {
      cookie: req.cookies['domain-a-cookie']
    }
  }

  res.status(200).send(response)
})

domain-b.com

router.get('/get-cookie', (req, res) => {
  fetch('https://www.domain-a.org/send-cookie', {
    method: 'GET',
    mode: 'cors',
    credentials: 'include'
  })
    .then(response => {
      if (response.ok) {
        return response.json()
      }

      return Promise.reject(response)
    })
    .then(result => {
      if (result.status === 'success' && result.data && result.data.cookie) {
        res.cookie('domain-b-cookie', result.data.cookie, {
          httpOnly: true,
          sameSite: 'none',
          secure: true,
          maxAge: 60 * 60 * 24 * 30
        })

        res.status(200).send({ status: 'success' })
      } else {
        res.status(500).send(result)
      }
    })
    .catch(error => {
      res.status(500).send(error)
    })
})

Solution

  • If a user logs in to www.domain-a.org, can I share that session and automatically log in that user on www.domain-b.com?

    Not with your approach.

    This is a very simple view of how cookies work for authentication:

    1. The user logs in to www.domain-a.org
    2. The user's browser stores a cookie with evidence that they are logged in
    3. Whenever they visit www.domain-a.org the browser sends that evidence to www.domain-a.org

    Later the user visits www.domain-b.org. The browser does not send the cookie to www.domain-b.org because it is a different website. It would be a horrible security problem if I logged into my online banking, then I visited your website and my browser sent you my bank's cookies. I don't know you. I certainly don't trust you with my bank account.

    Your server side code makes a request to www.domain-a.org. It isn't sending the cookie stored in the browser. It isn't the browser. It doesn't have that cookie. It would be a security nightmare if the browser had handed that cookie over.


    A general approach to doing single sign on would be:

    1. The user logs in to www.domain-a.org
    2. The user's browser stores a cookie with evidence that they are logged in
    3. The user visits www.domain-b.org which sends them a session cookie to identify them
    4. B presents the user with an option to use A to authenticate and the user picks that option by clicking on a link to (e.g.) http://www.domain-a.org/sso?redirect=http://www.domain-a.org/login
    5. The user visits http://www.domain-a.org/sso?redirect=http://www.domain-b.org/login which provides them with a prompt "Do you want to share your personal data with B?"
    6. They click yes and are sent to http://www.domain-b.org/login?token=some-auto-generated-token
    7. B then uses server side code to make an HTTP request to A asking "Is some-auto-generated-token? Can I have their personal data?"
    8. A sends the personal data to B
    9. B (finally) responds to the request (from step 6) going "Hi! I know who you are now.

    Yes. This is relatively complicated. There are a lot of checks. You're dealing with authentication/authorization here. Security is paramount.

    The standard for this is OAuth. Password.js has some features for acting as an OAuth client for Node.js.