In a fetch handler triggered by a page navigation, I tried to do this:
return event.respondWith(new Response('Hello!', {
headers: {
"Set-Cookie": "TestCookie=foo; path=/; Max-Age=60;"
"TestHeader": "foo"
}
}));
Then I loaded any URL in the browser, and got the "Hello!" body. In Chrome devtools, I see the TestHeader set in the network panel. But the cookie is not showing up in the network panel, nor in the Application > Cookies viewer. document.cookie also fails to produce it.
The request is initiated by a page navigation, so there's no opportunity to set credentials: "include" on the fetch from the browser tab.
Is it possible to add a cookie to a response in the ServiceWorker? If not, is it possible to write cookies in any other way?
There's some relevant information in the Fetch specification.
As per https://fetch.spec.whatwg.org/#forbidden-response-header-name:
A forbidden response-header name is a header name that is a
byte-case-insensitive match for one of:
Set-Cookie
Set-Cookie2
And then as per item 6 in https://fetch.spec.whatwg.org/#concept-headers-append:
Otherwise, if guard is "response" and name is a forbidden response-header name, return.
This restriction on adding in the Set-Cookie header applies to either constructing new Response objects with an initial set of headers, or adding in headers after the fact to an existing Response object.
There is a plan to add in support for reading and writing cookies inside of a service worker, but that will use a mechanism other than the Set-Cookie header in a Response object. There's more information about the plans in this GitHub issue.
You may try following:
async function handleRequest(request) {
let response = await fetch(request.url, request);
// Copy the response so that we can modify headers.
response = new Response(response.body, response)
response.headers.set("Set-Cookie", "test=1234");
return response;
}
Related
I am trying to call API to authenticate user. getting 200 success response but not getting JSESSIONID cookie in response.
axios.defaults.withCredentials = 'true';
axios.defaults.crossDomain = 'true';
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.defaults.headers.post['withCredentials'] = 'true';
const data = {
username: this.state.user,
credential: this.state.pass
}
axios.post('http://3.122.7.162:5000/v60/admin/session', data)
.then(response => {
console.log("response");
console.log(response);
})
.catch(error => {
console.log("error");
console.log(error);
});
Thanks
XMLHttpRequest and fetch (the built-in HTTP request libraries in browsers) do not expose information about cookies in their response object.
It isn't even available through directly reading the Set-Cookie header because it is a forbidden response header.
The only way to read cookie data is through document.cookie, but that only provides information about same origin cookies.
There is no direct way to get the data from a cross-origin cookie using Ajax methods. The only way to do it would be to change the server side code so it returned a copy of the cookie data through another path that the JavaScript could read (e.g. in the response body).
This doesn't stop you using the cookies. Unless third-party cookies are disabled, they will still be set in the browser's cookie jar and automatically sent with future requests to the domain they belong to.
What is the reason the server is returning object as 'undefined' and 'XMLHttpRequest cannot load the "URL" Response for preflight is invalid (redirect).
Flow of app - its just a normal post service sending document details to the server in return should return an object holding various parameters, but its returning 'undefined'
The service for posting the document
fileUpload: {
method: 'POST',
url: config.apiPath + 'employee/service/pushRecords', //this is the URL that should return an object with different set of parameters (currently its returning Error error [undefined])
isArray: false,
params: {},
headers: {
'content-type': undefined
}
},
above service i have used after creating formdata w.r.t document
function registerFormdata(files, fieldName) {
files = files || [];
fieldName = fieldName || 'FileSent';
var returnData = new FormData();
_.each(files, function (file, ind) {
returnData.append(fieldName,file);
});
return returnData;
}
now this is the controller where these services are used
function sendFilesToServer() {
var formData = employeePushService.registerFormdata(directive.dropZoneFile.fileToUpload);
return docUploadService.fileUpload(formData)
.then(function(document) {
// Extra actions but here the server should be returning an object with set of parameters but in browser console its Error [undefined]
}).catch(logger.error);
}
Assuming that the URL target in yout post is correct, it seems that you have a CORS problem, let me explain some things.
I don't know if the server side API it's developed by yourself, if it is, you need to add the CORS access, your server must return this header:
Access-Control-Allow-Origin: http://foo.example
You can replace http://foo.example by *, it means that all request origin will have access.
First, you need to know that when in the client you make an AJAX CORS request, your browser first do a request to the server to check if the server allow the request, this request is a OPTION method, you can see this if, for example in chrome, you enable the dev tools, there, in the network tab you can see that request.
So, in that OPTIONS request, the server must set in the response headers, the Access-Control-Allow-Origin header.
So, you must check this steps, your problem is that the server side is not allowing your request.
By the way, not all the content-type are supported in CORS request, here you have more information that sure will be helpfull.
Another link to be helpfull for the problem when a 302 happens due to a redirect. In that case, the POST response must also include the Access-Control-Allow-Origin header.
I am trying to access the header 'error-detail' as you can see in the browser network inspector (link above), the header gets returned. Server-wise I have also added the custom header to the 'Access-Control-Expose-Headers' to allow cross-domain requests as this was suggested to be the fix on other questions.
Below is the request to the server along with the success/error callbacks.
this.signon = function (request, onComplete, onError) {
console.log("Calling server with 'login' request...");
return $http.post("http://localhost:8080/markit-war/services/rest/UserService/login/", request)
.then(onComplete, onError);
};
var onLookupComplete = function(response) {
if (response.data.username)
{
//If user is returned, redirect to the dashboard.
$location.path('/dashboard');
}
$scope.username = response.data.username;
};
var onError = function(response) {
$scope.error = "Ooops, something went wrong..";
console.log('error-detail: ' + response.headers('error-detail'));
};
When I try access the response header as seen below:
console.log(response.headers());
console.log('error-detail: ' + response.headers('error-detail'));
This only outputs:
content-type: "application/json"
error-detail: null
Is there a reason why the error-detail header is not being mapped over to the response object?
I think you are on the right track. To have access to custom headers, your server needs to set this special Access-Control-Expose-Headers header, otherwise your browser will only allow access to 6 predefined header values as listed in the Mozilla docs.
In your screenshot such a header is not present in the response. You should have a look at the backend for this cors header to also be present in the response.
This is a CORS Issue. Because this is a cross-origin request, the browser is hiding most ot the headers. The server needs to include a Access-Control-Expose-Headers header in its response.
The Access-Control-Expose-Headers1 response header indicates which headers can be exposed as part of the response by listing their names.
By default, only the 6 simple response headers are exposed:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
If you want clients to be able to access other headers, you have to list them using the Access-Control-Expose-Headers header.
For more information, see MDN HTTP Header -- Access-Control-Expose-Headers
I am using navigator for communicating with the server , but problem is that we need to pass some header information as there is filter which recognise the request is from the valid source.
Can anybody help on this?
Thanks.
See the Navigator.sendBeacon MDN documentation for further information.
Create a blob to provide headers. Here is an example:
window.onunload = () => {
const body = {
id,
email,
};
const headers = {
type: 'application/json',
};
const blob = new Blob([JSON.stringify(body)], headers);
navigator.sendBeacon('url', blob);
};
navigator.sendBeacon will send a POST request with the Content-Type request header set to whatever is in headers.type. This seems to be the only header you can set in a beacon though, per W3C:
The sendBeacon method does not provide ability to customize the request method, provide custom request headers, or change other processing properties of the request and response. Applications that require non-default settings for such requests should use the [FETCH] API with keepalive flag set to true.
I was able to observe some of how this worked through this Chromium bug report.
As written in the Processing Model of sendBeacon :
Extract object's byte stream (transmittedData) and content type (contentType).
How extraction is performed is described here
What I've gathered is that the content type of the transmitted data is extracted, and it is set as the Content-Type of the HTTP request.
1) If a Blob object is sent, the Content-Type becomes the Blob's type.
2) If a FormData object is sent, the Content-Type becomes multipart/form-data
3) If a URLSearchParams object is sent, the Content-Type becomes application/x-www-form-urlencoded
4) If a normal string is sent, the Content-Type becomes text/plain
Javascript code to implement different objects can be found here
If you're using Chrome and you're trying to set the content-type header, you'll probably have some issues due to security restrictions:
Uncaught DOMException: Failed to execute 'sendBeacon' on 'Navigator': sendBeacon() with a Blob whose type is not any of the CORS-safelisted values for the Content-Type request header is disabled temporarily. See http://crbug.com/490015 for details.
See sendBeacon API not working temporarily due to security issue, any workaround?
I want to call an api when someone close the tab, so I tried to use navigator.sendBeacon() but the problem is we need to pass the Authorization token into it and sendBeacon does not provide that, so I found other solution that is more effective and very easy to implement.
The solution is a native fetch API with a keepalive flag in pagehide event.
Code
window.addEventListener('pagehide', () => {
fetch(`<URL>`, {
keepalive: true,
method: '<METHOD>',
headers: {
'content-type': 'application/json',
// any header you can pass here
},
body: JSON.stringify({ data: 'any data' }),
});
});
FAQs / TL;DR Version
Why should we need to use the keepalive flag?
The keepalive option can be used to allow the request to outlive the page. Fetch with the keepalive flag is a replacement for the Navigator.sendBeacon() API.
Learn more about it, please visit https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters
What is PageLifecycle API
Learn more about it, please visit https://developer.chrome.com/blog/page-lifecycle-api/
From the Page Lifecycle image, shouldn't unload be considered as the best choice?
unload is the best event for this case but unload is not firing in some cases on mobile and it also does not support the bfcache functionality.
I also notice that when I am using unload then I am not getting proper output in the server log. why? IDK, if you know about it then comments are welcome.
Nowadays, It's also not recommended by the developers.
Learn more about why unload is not recommended: https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes
Learn more about pagehide: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
Because the method sendBeacon(..) does not allow headers manipulation, I added them into the form as normal fields:
const formData = new FormData();
formData.append('authorization', myAuthService.getCachedToken());
navigator.sendBeacon(myURL, formData);
Then on the host side I added a simple Middleware class (.Net) which catches POST requests without headers and copies them from the body:
public class AuthMiddleware
{
...
...
public async Task Invoke(HttpContext context)
{
string authHeader = context.Request.Headers["Authorization"];
if (authHeader == null && context.Request.Method=="POST")
{
context.Request.Headers["Authorization"] = string.Format("Bearer {0}",
context.Request.Form["authorization"].ToString());
}
await _next.Invoke(context);
}
}
Posting as an answer as I'm not allowed to post a comment under the answer:
For Chrome, issue with navigator.sendBeacon sending Blob for with non CORS-safelisted types was fixed in Chrome version 81 so this should be safe to use now.
https://bugs.chromium.org/p/chromium/issues/detail?id=724929
For IE, an alternative in unload event is to use synchronous ajax request, as IE doesn't support sendBeacon but supports synchronous ajax call in my case.
You can't send data with JSON after Chrome 39, has been disabled due to a security concern.
You can try to send data with plain text. But don't forget the parseing text from the backend.
After searching for an answer for this question I found out that for passing header with navigator we need to pass a blob object.
For example
var headers = {type: 'application/json'};
var blob = new Blob(request, headers);
navigator.sendBeacon('url/to/send', blob);
I'm using the fetch API to make a cross-domain request similar to the below snippet
window.fetch('http://data.test.wikibus.org/magazines', { method: 'get'})
.then(function(response) {
var linkHeader = response.headers.get('Link');
document.querySelector('#link-header').innerText = 'The Link header is: ' + linkHeader;
});
<span id="link-header"></span>
As you see the Link header (and some other headers too) is not accessible although it is returned in the response. I assume that's a CORS issue, because on local requests all headers are accessible.
Is that by design? Is there a way around that problem?
The resource you are requesting most likely lacks a Access-Control-Expose-Headers header that contains Link as value.
See https://fetch.spec.whatwg.org/#http-access-control-expose-headers and see https://fetch.spec.whatwg.org/#concept-filtered-response-cors for the details of which headers get filtered out of a CORS response.