Chrome Extension fetch function not sending cookies - laravel

I have a chrome extension that does an ajax call using the fetch function to my server running laravel.
manifest perfmissions
"permissions": [
"webRequest",
"webRequestBlocking",
"webNavigation",
"activeTab",
"tabs",
"cookies",
"<all_urls>"
],
fetch call
fetch(this.url, {
credentials: 'include',
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded"
},
body: encodeDataToURL(telemetry)
})
.then(function (data) {
console.log('Request succeeded with JSON response', data);
})
.catch(function (error) {
console.log('Request failed', error);
});
Cookies
siusession=eyJpdiI6InlRS2wyb1BCZnJSSGtUaXVRelV4M3c9PSIsInZhbHVlIjoiRThteUk4MmVxeXV6a1N5ZUxTaFpxcUtSazJQRE1ZUUNQUWlBREVTdHRQM2pjNEVJUVUxd3gwM1JZMDNjOXR2TyIsIm1hYyI6ImUwMGQyNmAAhwQ3YWQ4YzRhOWVhYTk2ZjI2NDgwNTljNDE2YWU5NTdlZWM1MThiZWJjYzI3NmZjZWRhOGRlMzIifQ%3D%3D; expires=Tue, 25-Sep-2018 04:28:56 GMT; Max-Age=28800; path=/; secure; httponly; samesite=lax
I have a opened session on my browser to that domain, which makes me having cookies with the Session ID and XSRF-TOKEN.
The problem is, it doesn't send the cookies with the call.
And on firefox, the same exact code and manifest it does send the browser cookies with the call.
What can be wrong? Does chrome require some sort of different permissions or another way to make the call including cookies?

Assuming the fetch call is made from the background script, you'll need to query the cookies and insert those in the http header.

Related

AWS Lambda Set-Cookie header not setting in the browser

I'm trying to set a cookie in my aws lambda function response. I don't have any header mapping as I'm using lambda proxy integration with API Gateway. The response code looks like this in the lambda function:
exports.handler = async (event) => {
const response = {
statusCode: 200,
"multiValueHeaders": {
"Set-Cookie": ["gtgm=6c7729687d5ff1a05f1a5dfb15ce3b8fa3f2b590; path=/; expires=Fri, 13-Feb-2032 13:27:44 GMT; secure; HttpOnly; SameSite=None"]
},
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Credentials": true,
},
};
return response;
};
I use fiddler to check the response and I can see the "Set-Cookie..." in the response which leads me to believe that the code above is correct? The issue is that the browser just ignores it and doesn't set any cookies at all except for the AWS DNT cookie. I'm not sure what else to check or if I've missed anything in the cookie config.
This is what my request looks like:
<Button
onClick={() => {
fetch(
"https:mysupercoolapi.com/cookie-test/",
{
// credentials: "include",
headers: {
"Content-Type": "application/json",
// "Access-Control-Allow-Credentials": "true",
},
}
)
.then((response) => response.json())
.then((data) => {
console.log(data);
});
}}
>
Cookie Test
</Button>
Not sure what I'm missing or where I'm going wrong.
I magaed to figure this one out myself. The setup above was fine but the browser requires you to have the following set properly in order for it to actually set the cookie:
In the request: Set "credentials" as include. Depending on what library you're using (fetch would be credentials: "include"). You can see it is commented out in my original post above which is not correct.
In the Response header in the Lambda function you need to set you origin e.g., "Access-Control-Allow-Origin": "http://localhost:3002". After testing I switched it to my actual domain. If there's a way to keep this set to the actual domain but still have it work while testing on localhost please let me know.
In API Gateway you need to set the CORS values as in the screenshot below to ensure the preflight (OPTINOS) call is made correctly as well.
The key is to have both the request and response headers configured correctly. This is harder to do when using something like AWS but much easier with something like express.js where you can use the middleware.

Session cookie is not set in browser

I have frontend client running on custom Next.js server that is fetching data with apollo client.
My backend is graphql-yoga with prisma utilizing express-session.
I have problem with picking correct CORS settings for my client and backend so cookie would be properly set in the browser, especially on heroku.
Currently I am sending graphql request from client with apollo-client having option
credentials: "include" but also have tried "same-origin" with no better result.
I can see cookie client in response from my backend in Set-Cookie header, and in devTools/application/cookies. But this cookie is transparent to browser and is lost on refresh.
With this said I also tried to implement some afterware to apollo client as apolloLink that would intercept cookie from response.headers but it is empty.
So far now I'm thinking about pursuing those two paths of resolving the issue.
(I'm only implementing CORS because browser prevents fetching data without it.)
CLIENT
ApolloClient config for client-side:
const httpLink = new HttpLink({
credentials: "include",
uri: BACKEND_ENDPOINT,
});
Client CORS usage and config:
app
.prepare()
.then(() => {
const server = express()
.use(cors(corsOptions))
.options("*", cors(corsOptions))
.set("trust proxy", 1);
...here goes rest of implementation
const corsOptions = {
origin: [process.env.BACKEND_ENDPOINT, process.env.HEROKU_CORS_URL],
credentials: true,
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie", '*'],
methods: "GET,POST",
optionsSuccessStatus: 200
};
My atempt to get headers from response in apolloClient(but headers are empty and data is not fetched afterwards):
const middlewareLink = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
const context = operation.getContext();
const {response: {headers}} = context;
if (headers) {
const cookie = response.headers.get("set-cookie");
if (cookie) {
//set cookie here
}
}
return response;
});
});
BACKEND
CORS implementaion (remeber that is gql-yoga so I need first to expose express from it)
server.express
.use(cors(corsOptions))
.options("*", cors())
.set("trust proxy", 1);
...here goes rest of implementation
const corsOptions = {
origin: [process.env.CLIENT_URL_DEV, process.env.CLIENT_URL_PROD, process.env.HEROKU_CORS_URL],
credentials: true,
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie"],
exposedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie"],
methods: "GET,HEAD,PUT,PATCH,POST,OPTIONS",
optionsSuccessStatus: 200
};
Session settings, store is connect-redis
server.express
.use(
session({
store: store,
genid: () => uuidv4(v4options),
name: process.env.SESSION_NAME,
secret: process.env.SESSION_SECRET,
resave: true,
rolling: true,
saveUninitialized: false,
sameSite: false,
proxy: STAGE,
unset: "destroy",
cookie: {
httpOnly: true,
path: "/",
secure: STAGE,
maxAge: STAGE ? TTL_PROD : TTL_DEV
}
})
)
I am expecting session cookie to be set on the client after authentication.
Actual result:
Cookie is visible only in Set-Cookie response header but is transparent to browser and not persistent nor set (lost on refresh or page change). Funny enough I can still make authenticated requests for data.
This may not be a CORS issue, it looks like a third-party cookie problem.
Behaviour could be different across browsers so I recommend testing several ones. Firefox (as of version 77) seems to be less restrictive. In Chrome (as of version 83) there is an indicator on the far right of the URL bar when a third party cookie has been blocked. You can confirm whether third party cookies is the cause of the problem by creating an exception for the current website.
Assuming your current setup is as follows:
frontend.com
backend.herokuapp.com
Using a custom domain for your backend that is a subdomain of your frontend would solve your problem:
frontend.com
api.frontend.com
The following setup wouldn't work because herokuapp.com is included in the Mozilla Foundation’s Public Suffix List:
frontend.herokuapp.com
backend.herokuapp.com
More details on Heroku.

Cross Origin Put Request Methods

What are some successful methods for performing Cross Origin Put requests? I successfully used a Proxy to make a GET request and put it into a Dropdown list as can be seen here >> Create Dropdown list from API Query >>but have not been able to use the same process in making a PUT Request?
Thoughts?
I was able to successfully get a PUT request to work just peachy through the use of the proxy in javascript.
$.ajaxPrefilter( function (options) {
if (options.crossDomain && jQuery.support.cors) {
var http = (window.location.protocol === 'http:' ? 'http:' : 'https:');
options.url = http + '//cors-anywhere.herokuapp.com/' + options.url;
//options.url = "http://cors.corsproxy.io/url=" + options.url;
}
});
Once the Proxy was established, I used the chrome extension (now a desktop app) Postman to get the PUT HTML code. This was done by first getting the PUT request to work in Postman and then selecting the "code" link (below the "send" button) and selecting "JavaScript Jquery AJAX" from the drop-down. Here is an example of outputted code from Postman.
var settings = {
"async": true,
"crossDomain": true,
"url": "https://[apiurl].com",
"method": "PUT",
"headers": {
"content-type": "text/xml",
"cache-control": "no-cache",
"postman-token": "[token]"
},
"data": "<this_is_the_xml_data_youre_sending>"
}
$.ajax(settings).done(function (response) {
console.log(response);
});
Once the code is copied from Postman, put the proxy code and Postman javascript into an HTML page and watch the PUT request happen.

Cookie is "undefined" in Koa/Express request

I'm using Koa JS framework for jwt authentication.
So basically when the user signs in, I set a jwt token (signed) to user's browser cookie, which seems to work fine as shown below (Chrome cookie settings):
(www.localhost.com instead of localhost is because I edited my hostfile, but this should have no effect in setting/getting cookies)
The problem, however, is when I send a POST request to my local Koa server, the jwt cookie is undefined. All I'm doing to verify the token is this:
routes.js
const Router = require("koa-router");
const router = new Router();
router.post(`api/authenticate`, function* () {
const jwt = this.cookies.get("jwt", { signed: true }); //jwt is undefined!!
if (!jwt)
this.throw("Invalid or expired token!");
this.status = 200;
});
//...
app.use(router.routes());
app.use(router.allowedMethods());
Here, this.cookies.get("jwt") returns undefined. The POST request is sent using AXIOS library with "withCredentials: true" header and a valid CSRF token:
authenticate.js
axios.post("api/authenticate", {}, {
headers: {
"X-Requested-With": "XMLHttpRequest",
"X-CSRF-Token": "A VALID CSRF TOKEN GENERATED BY SERVER",
"Content-Type": "application/json",
},
withCredentials: true,
});
Can anyone help me find out why this.cookies.get fails to fetch cookie from a simple POST request? I'm simply posting to my localhost, so I believe this is not a CORS problem.
What is more strange is that when I check my chrome developer tool, the "jwt" and "jwt.sig" tokens are successfully included in the request header..
Any help would be appreciated.
Update: Setting the cookie
//...
this.cookies.set("jwt", "SOME JWT GENERATED BY SERVER", {
httpOnly: true,
signed: true,
});
//...

Set-Cookie on Browser with Ajax Request via CORS

Attempting to implement an ajax login / signup process (no refresh site with authentication). Using cookies for preserving state. I thought I'd have this right by now but for some reason browser doesn't set cookies after it gets them back from the server. Can anyone help? Here are the request and response headers:
Request URL:http://api.site.dev/v1/login
Request Method:POST
Status Code:200 OK
Request Headers
Accept:application/json, text/plain, */*
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:57
Content-Type:application/json;charset=UTF-8
Host:api.site.dev
Origin:http://site.dev
Referer:http://site.dev/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
withCredentials:true
X-Requested-With:XMLHttpRequest
Request Payload
{"email":"calvinfroedge#gmail.com","password":"foobar"}
Response Headers
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:X-Requested-With, Content-Type, withCredentials
Access-Control-Allow-Methods:GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin:http://site.dev
Connection:Keep-Alive
Content-Length:19
Content-Type:application/json
Date:Tue, 08 Jan 2013 18:23:14 GMT
Keep-Alive:timeout=5, max=99
Server:Apache/2.2.22 (Unix) DAV/2 PHP/5.4.7 mod_ssl/2.2.22 OpenSSL/0.9.8r
Set-Cookie:site=%2B1THQQ%2BbZkEwTYFvXFVV5fxi00l2K%2B6fvt9SuHACTNsEwUGzDSUckt38ZeDsNbZSsqzHmPMWRLc84eDLZzh8%2Fw%3D%3D; expires=Thu, 10-Jan-2013 18:23:14 GMT; path=/; domain=.site.dev; httponly
X-Powered-By:PHP/5.4.7
I also see the cookie in chrome network tools, as returned from the server:
Response Cookies
Name: site
Value: %2B1THQQ%2BbZkEwTYFvXFVV5fxi00l2K%2B6fvt9SuHACTNsEwUGzDSUckt38ZeDsNbZSsqzHmPMWRLc84eDLZzh8%2Fw%3D%3D
Domain: .site.dev
Path: /
Expires: Session
Size: 196
Http: ✓
Your AJAX request must be made with the "withCredentials" settings set to true (only available in XmlHttpRequest2 and fetch):
var req = new XMLHttpRequest();
req.open('GET', 'https://api.bobank.com/accounts', true); // force XMLHttpRequest2
req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
req.setRequestHeader('Accept', 'application/json');
req.withCredentials = true; // pass along cookies
req.onload = function() {
// store token and redirect
let json;
try {
json = JSON.parse(req.responseText);
} catch (error) {
return reject(error);
}
resolve(json);
};
req.onerror = reject;
If you want a detailed explanation on CORS, API security, and cookies, the answer doesn't fit in a StackOverflow comment. Check out this article I wrote on the subject: http://www.redotheweb.com/2015/11/09/api-security.html
I had a similiar problem, and it turned out that the browser settings were blocking third-party cookies (Chrome > Settings > Advanced Settings > Privacy > Content Settings > Block third-party cookies and site data). Unblocking solved the problem!
I needed to pass cookies from multiple subdomains to a single API domain using AJAX and PHP and handeling CORS correctly.
This was the challenge and solution:
1 - Backend PHP on api.example.com.
2 - Multiple JS front ends such as one.example.com, two.example.com etc.
3 - Cookies needed to be passed both ways.
4 - AJAX call from multiple front-ends to PHP backend on api.example.com
5 - In PHP, I do not prefer to use $_SERVER["HTTP_ORIGIN"], not always reliable/safe in my opinion (I had some browsers where HTTP-ORIGIN was always empty).
The normal way to do this in PHP with single front end domain is starting PHP code with:
header('Access-Control-Allow-Origin: https://one.example.com');
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');
header('Access-Control-Allow-Credentials: true');
And in JS on one.example.com domain:
jQuery.ajax({
url: myURL,
type: "POST",
xhrFields: {withCredentials: true},
dataType: "text",
contentType: "text/xml; charset=\"utf-8\"",
cache: false,
headers: "",
data: myCallJSONStr,
success: function(myResponse) {.....}
However, this is not workable as I am using multiple subdomains to call my API domain.
And this solution will NOT work as I want to pass on cookies:
header('Access-Control-Allow-Origin: *');
It conflicts with the pass on cookie setting on the JS site:
xhrFields: {withCredentials: true}
Here is what I did:
1 - use GET parameter to pass the Subdomain.
2 - Hardcode the Main domain in PHP so only (all) Subdomains are allowed.
This is the JS/JQuery AJAX part of my solution:
function getSubDomain(){
let mySubDomain = "";
let myDomain = window.location.host;
let myArrayParts = myDomain.split(".");
if (myArrayParts.length == 3){
mySubDomain = myArrayParts[0];
}
return mySubDomain;
}
And in the AJAX call:
let mySubDomain = getSubDomain();
if (mySubDomain != ""){
myURL += "?source=" + mySubDomain + "&end"; //use & instead of ? if URL already has GET parameters
}
jQuery.ajax({
url: myURL,
type: "POST",
xhrFields: {withCredentials: true},
dataType: "text",
contentType: "text/xml; charset=\"utf-8\"",
cache: false,
headers: "",
data: myCallJSONStr,
success: function(myResponse) {.....}
Finally, the PHP part:
<?php
$myDomain = "example.com";
$mySubdomain = "";
if (isset($_GET["source"])) {
$mySubdomain = $_GET["source"].".";
}
$myDomainAllowOrigin = "https://".$mySubdomain.$myDomain;
$myAllowOrigin = "Access-Control-Allow-Origin: ".$myDomainAllowOrigin;
//echo $myAllowOrigin;
header($myAllowOrigin);
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');
header('Access-Control-Allow-Credentials: true');
IMPORTANT, don't forget to set the cookies for all subdomains, in this case the domain for the cookie would be: .example.com (so with a dot in front of the main domain):
<?php
//////////////// GLOBALS /////////////////////////////////
$gCookieDomain = ".example.com";
$gCookieValidForDays = 90;
//////////////// COOKIE FUNTIONS /////////////////////////////////
function setAPCookie($myCookieName, $myCookieValue, $myHttponly){
global $gCookieDomain;
global $gCookieValidForDays;
$myExpires = time()+60*60*24*$gCookieValidForDays;
setcookie($myCookieName, $myCookieValue, $myExpires, "/", $gCookieDomain, true, $myHttponly);
return $myExpires;
}
This solution allows me to call the API on api.example.com from any subdomains on example.com.
NB. for situation where there is only a single calling subdomain, I prefer using .htaccess for setting CORS instead of PHP. Here is an example of .htaccess (linux/apache) for only one.example.com calling api.example.com:
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "https://one.example.com"
Header set Access-Control-Allow-Headers "Origin, Content-Type, X-Auth-Token"
Header set Access-Control-Allow-Credentials "true"
</IfModule>
And place this .htaccess in the root of api.example.com.

Resources