2

I have some trouble working with cookies via chrome extension from popup script.

popup.js content:

document.addEventListener('DOMContentLoaded', () => {
    function cookieinfo() {
        chrome.cookies.getAll({url: 'http://localhost:8080'}, function(cookie) {
            console.log('Found cookie: ', cookie)
            if (cookie == null)
                return;

            fetch('http://localhost:8080', {credentials: 'include'}).then((response) => {
                // do some stuff
                return response;
            });
        });
    }
    window.onload=cookieinfo;
}, false);

Steps that I perform:

  1. Log into my application on localhost (So I get the cookies)
  2. Open the popup (so popup.js is executed)
  3. I see in the console log that chrome found necessary cookies
  4. Server says that ingoing request has empty cookies
  5. I refresh page of localhost application
  6. I am logged out now

Maybe someone knows what I'm doing wrong?

Edit:

It seems that the reason is that my cookie has parameters HttpOnly=true and SameSite=Lax (related link). I can see another cookies in the server log. But due to this thread all cookies will be sent if credentials parameter is set to include, even httpOnly cookies. Also I tried to send it to 127.0.0.1 instead of localhost due to this answer with the same result.

I can't set httpOnly to false. This is forced by framework. Somebody know how to fix it?

Edit2:

I finally installed Cookie editor and found out that the SameSite=Lax is the reason. If I set it to No Restriction then I will see it on the server side. Unfortunately, the framework I'm using only allows Lax and Strict options (Chrome extension fails with both). Does anyone know how to send Lax cookies from the Chrome extension?

Defake
  • 373
  • 4
  • 15
  • 1
    I would try XMLHttpRequest. – wOxxOm Aug 07 '18 at 16:23
  • Isn't the CPU thread that popup.js runs on separate from thread the main browser window runs on? I think you're setting cookies in your extension's browser instance, but you need to set the cookie's in the actual browser window context. – TJBlackman Aug 07 '18 at 17:23
  • @TJBlackman I tried to do this in content.js and background.js with the same result. Or do you mean another thing? – Defake Aug 07 '18 at 17:56
  • @TJBlackman it seems, you're right, because `document.cookie` returns empty string. But how do I run the code in the browser window context if the `content_script` is not the solution? – Defake Aug 09 '18 at 12:16
  • @wOxxOm no difference. `fetch`, `XMLHttpRequest` and `$.ajax` work with the same result (don't send this cookie) – Defake Aug 21 '18 at 12:20
  • 3
    Here is a related Chromium bug: https://bugs.chromium.org/p/chromium/issues/detail?id=617198 – SeinopSys Jun 05 '19 at 17:50

3 Answers3

5

This was the issue with extensions in Chromium till version 77. When cross-site cookie was set to SameSite=Lax or SameSite=Strict, the cookie was not sent with the cross-site request.

This has been fixed in version 78 in all platforms. Now chrome extension sends cookies when SameSite=Lax or SameSite=Strict.

References:

https://bugs.chromium.org/p/chromium/issues/detail?id=1007973

https://chromium-review.googlesource.com/c/chromium/src/+/1827503

https://bugs.chromium.org/p/chromium/issues/detail?id=617198

Ganapati V S
  • 1,571
  • 1
  • 12
  • 23
  • Does this mean that cross-site cookies are *always* sent from Chrome extensions, and that [the coming SameSite changes](https://www.chromestatus.com/feature/5088147346030592) will actually have no effect for extensions? – Don't Panic Jan 07 '20 at 12:51
  • Yes, AFAIK cross-site cookies were always sent in the older versions of chrome extensions(only when they don't have `SameSite` attribute of course). This is how most of the website chrome extensions used to work using website cookies without many changes. – Ganapati V S Jan 07 '20 at 15:38
1

What I found out is the cookie's path is crucial. Any mismatch results in misleading behaviour.

This is my setup:

  • backend server running at localhost:8081
  • chrome manifest permission has "http://localhost:8081/"
  • backend returns cookie with path=/, eg. this is a sample response header Set-Cookie: refresh_token=bar; Path=/; SameSite=Lax; HttpOnly
  • chrome extension can query the cookie manually: chrome.cookies.get({ url: 'http://localhost:8081/', name: 'refresh_token' }...
  • chrome extension automatically attaches the cookie when you sending to other url paths under localhost:8081, eg:
    fetch('http://localhost:8081/v1/meh').then((response) => {
        console.log(response);
    })
    
    Server side will see the refresh_token cookie.

To summarise: a cookie set at path /a won't be sent to a url at path /b; a cookie set at path / will be sent to all urls under same domain.

xysun
  • 1,995
  • 18
  • 18
0

The content script is 100% the solution.

You basically have two individual browsers, the regular browser and also the extension popup browser. But they are entirely separate and can only send messages back and forth. So what you need to do is have the extension context send a message to the browser context that instructs some code in that context to get the document.cookies and send them back to the extension context.

Here's an example of me getting cookies from each separate browser context.

manifest.json

{
  "manifest_version": 2,
  "name": "Cookie Monster",
  "description": "Nom nom nom nom",
  "version": "1.0",
  "browser_action": {
    "default_popup": "html/extension.html",
    "default_title":"Cookie Monster"
  },
  "permissions": [
    "activeTab",
    "tabs",
    "http://*/*",
    "https://*/*"
 ],
  "content_scripts": [{
    "js":["/js/client.js"],
    "matches":["http://*/*","https://*/*"]
  }]
}

extension.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Cookies</title>
    <style>
      body {
        display: block; 
        min-height: 250px; 
        width: 250px; 
        padding: 5px; 
      }
      button {
        display: block; 
        margin: 0 0 10px 0; 
      }
    </style>
  </head>
  <body class="container">
    <h1>Cookies</h1>
    <button id="extension_cookies" type="button">Get PopUp Cookies</button>
    <button id="browser_cookies" type="button">Get Browser Cookies</button>
    <p id="result"></p>

    <script src="/js/extension.js" type="text/javascript"></script>
  </body>
</html>

extension.js

'use strict';
(function(){
    // cache import DOM elements
    const extension_btn = document.querySelector('#extension_cookies');
    const browser_btn = document.querySelector('#browser_cookies'); 
    const result = document.querySelector('#result');


    // runs in the popup window of the extension, 
    // which is it's own browser context 
    // and has it's own set of cookies
    extension_btn.addEventListener('click', () => {
        if (document.cookie === ''){
            result.innerText = 'No Cookies...';
        } else {
            result.innerText = document.cookie;
        }
    })

    // send message to browser context
    // message will inform browser client of what to do
    // the browser then needs to pass data to the callback function
    // then we can display results
    browser_btn.addEventListener('click', () => {
        chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
            chrome.tabs.sendMessage(tabs[0].id, {message: 'GET_COOKIES'}, (data) => {
                result.innerText = data.cookies
            });
        });
    })
}());

client.js

'use strict';
(function(){

  // receive a callback function so I can pass data to extension
  // get document cookies, put into an object
  // use callback to send response to extension
  const get_browser_cookies = (sendResponse) => {
    const cookies = document.cookie; 
    console.clear(); 
    console.log(cookies);
    sendResponse({ cookies: cookies }); 
  }


  // listen for messages from extension
  // a switch statement can help run only the correct function
  // must pass the function a reference to the sendResponse function
  // so I can pass data back to extension
  chrome.runtime.onMessage.addListener(function(data_from_extension, sender, sendResponse){
    switch (data_from_extension.message){
      case 'GET_COOKIES': {
        get_browser_cookies(sendResponse); 
        break; 
      }
      default: null; 
    }
  });
}())
TJBlackman
  • 1,895
  • 3
  • 20
  • 46
  • I tried, no difference. I updated the question, it seems that the reason is httpOnly flag. But I still don't know how to solve it – Defake Aug 13 '18 at 11:47
  • I just checked Grammarly chrome extension. The extension works well with their httponly "grauth" cookie and it doesn't ask for any other authorization. So, it's somehow possible – Defake Aug 20 '18 at 11:02
  • 1
    Well, you can inspect their extension's code and find out how they did it! http://www.chrisle.me/2012/12/how-to-get-the-source-code-to-any-chrome-extension/ – TJBlackman Aug 21 '18 at 12:51
  • wow, thanks for the trick. But this was my mistake again. My cookie has `SameSite=Lax` parameter, but theirs does not. So I updated the question again... – Defake Aug 21 '18 at 13:55
  • Was anyone able to find the solution for this? I have read tons of SO questions and tried multiple different things, but to no luck. Does anyone have a solution to this? – Praful Bagai May 29 '19 at 21:19
  • It's a kind of old question - you might be better off asking your own question that's specific to your use case. the javascript `fetch()` api has a `headers: { credentials: 'include'}` option that will allows HTTP only cookies to be sent along with the fetch request. Might help you..... – TJBlackman May 30 '19 at 15:33