CORS / Cross Origin Isolation / Google APIs - nginx

I'm trying to integrate the Zoom Web Video SDK into an existing web application, and SharedArrayBuffer has become a requirement for performance reasons, and in order to enable it the site has to implement Cross Origin Isolation. I've gone ahead and added the requisite configuration to NGINX, namely:
add_header 'Cross-Origin-Embedder-Policy' 'require-corp';
add_header 'Cross-Origin-Opener-Policy' 'same-origin';
... but of course this has knock-on effects for the rest of the previously existing and working site. The Google APIs won't seem to load successfully anymore.
I changed my index.html to add the crossorigin attribute to the Google API script tag as follows:
<script src="https://apis.google.com/js/platform.js" async defer crossorigin></script>
... and in my javascript source code I have (paraphrasing and reducing complexity to get the the point):
gapi.load('client:auth2', function() {
gapi.client
.init({
client_id: 'MY-CLIENT-ID',
cookiepolicy: 'single_host_origin',
discoveryDocs: ['https://classroom.googleapis.com/$discovery/rest?version=v1'],
scope: 'profile email'
})
.then(() => console.log('init finished'))
.catch(e) => console.error('init failed', e));
});
In that code, gapi and gapi.client are well-defined, but the init call never completes (no console logs from the then or from the catch). Looking at the network tab in devtools shows a failed GET request to:
https://content-classroom.googleapis.com/static/proxy.html?usegapi=1& ... and bunch of other stuff i'm not sure if is sensitive so am omitting
When you dive into the response, it shows:
To use this resource from a different origin, the server needs to
specify a cross-origin resource policy in the response headers:
Cross-Origin-Resource-Policy: same-siteChoose this option if the resource and the document are served from the same site.
Cross-Origin-Resource-Policy: cross-originOnly choose this option if an arbitrary website including this resource does not impose a
security risk.
Obviously, I can't control what Google's servers do, but can anyone instruct me as to how I can get this to work correctly. This only goes awry in the presence of my NGINX configuration change at the start of this post.
Update
I partially worked around this by fetching the discovery document for the API separately and passing the result into the gapi.client.init method instead of the URL. However, while I don't get the aforementioned outcome in the network tab of devtools anymore, but I instead get weird / inconsistent results with responses like "popup_closed_by_user" and "popup_closed_by_browser" happening in response to my GoogleAuth.signIn call. If I remove the headers from NGINX it starts behaving as expected again. I don't understand what's going on with this.

Related

Automatic sign-in not working on server URL (localhost works)

I'm trying to sign in automatically when possible using following code (TypeScript, called from a React app):
google.accounts.id.initialize({
client_id: envSettings.auth.google.clientId,
callback: signInWithJwt,
auto_select: true,
});
google.accounts.id.renderButton(domElement, {
theme: "outline",
});
google.accounts.id.prompt();
I now have following situation:
Signing in via the rendered button always works (locally and on my "Static Web App" hosted in Azure)
google.accounts.id.prompt() however only works on localhost but not on the server, even though the URLs are added in the "Authorized JavaScript origins" section in the Google console. I get following message in the browser console: [GSI_LOGGER]: The given origin is not allowed for the given client ID.
The only difference I see between localhost and the server is that server is running on https and localhost is using http.
For me this does not really make sense, as obviously it does work with the button. Any thoughts on what is wrong here?
You need to follow the message, "The given origin is not allowed for the given client ID." Go to the google cloud console, and allow the origin that your server is on. Go to your project > APIs and Services > Credentials > your OAuth 2.0 Client ID, and edit it to allow your domain to be authorized.
This is for security purposes, so that a malicious actor cannot use your client ID to pose as your app on another domain, and access your users' data.
Google documentation
Found the issue thanks to this post: https://stackoverflow.com/a/70739451/4092115
I had to set the referrer policy in my index.html as follows:
<meta name="referrer" content="strict-origin-when-cross-origin" />
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy

Cypress throws "cross origin error happened on page load" even if host didn't changed

I'm running Cypress tests on https://localhost:3000, which is my CYPRESS_BASE_URL also. Navigating to / redirects to /en internally, which works fine. But the test that I'm writing is about a form which builds a new URL, like https://localhost:3000/foobar?param=value. This works finde, I can even see the page that I'm redirecting to. But Cypress complains about this:
Cypress detected a cross origin error happened on page load:
> Blocked a frame with origin "https://localhost:3000" from accessing a cross-origin frame.
Before the page load, you were bound to the origin policy:
> https://localhost:3000
A cross origin error happens when your application navigates to a new URL which does not match the origin policy above.
A new URL does not match the origin policy if the 'protocol', 'port' (if specified), and/or 'host' (unless of the same superdomain) are different.
Cypress does not allow you to navigate to a different origin URL within a single test.
You may need to restructure some of your test code to avoid this problem.
Alternatively you can also disable Chrome Web Security in Chromium-based browsers which will turn off this restriction by setting { chromeWebSecurity: false } in cypress.json.
I do not want to disable chromeWebSecurity (which works), since I'm running this test on Firefox also. The only thing I can imagine is the way I do the redirect: window.location.href = "/foobar?param=value".
The error message is about changing protocol, port or host, but I'm doing none of them, and my SSL certificate is a valid one.
Could this be a bug or did I overlooked something?
A CORS error indicates that you might be performing requests to a different origin. An origin is the combination of protocol (http, https), domain (myapp.com, localhost), and port (80, 443, 3000).
So, all these are different origins:
http://localhost
https://localhost
http://localhost:3000
Even if they are all in localhost, they use different protocols or ports, so, they are different "origins". Two objects have the same origin only when the protocol, domain and port all match.
In Cypress, this is a common issue for many others when performing redirections, for instance, as in your case (see the discussion here). As per the documentation:
Cypress detected a cross-origin error happened on page load
This error means that your application navigated to a superdomain that Cypress was not bound to. Initially when you cy.visit(), Cypress changes the browser's URL to match the url passed to cy.visit(). This enables Cypress to communicate with your application to bypass all same-origin security policies among other things.
When your application navigates to a superdomain outside of the current origin-policy, Cypress is unable to communicate with it, and thus fails.
If you find yourself stuck and can't work around these issues you can
set chromeWebSecurity to false in your configuration file
(cypress.json by default) when running in Chrome family browsers (this
setting will not work in other browsers). Before doing so, you should
really understand and read about the reasoning here.
{"chromeWebSecurity": false}
Also, as described here:
If you attempt to visit two different superdomains, Cypress will
error. Visiting subdomains works fine. You can visit different
superdomains in different tests, but not in the same test.
Thus, although you are visting a subdomain, you might want to consider the following, as described in the documentation, which is used for visiting different superdomains:
it('navigates', () => {
cy.visit('https://localhost:3000')
})
it('navigates to new origin', () => {
cy.visit('https://localhost:3000/foobar?param=value')
})
Additionally, in the same documentation:
Although Cypress tries to enforce this limitation, it is possible for
your application to bypass Cypress's ability to detect this.
Examples of test cases that will error due to superdomain limitations
.click() an <a> with an href to a different superdomain.
.submit() a <form> that causes your web server to redirect to you a different superdomain.
Issue a JavaScript redirect in your application, such as window.location.href = '...', to a different superdomain.
In each of these situations, Cypress will lose the ability to automate your application and will immediately error.
See common workarounds.
See Does Cross-Origin Resource Sharing(CORS) differentiate between HTTP AND HTTPS? for a definition of superdomain
Yes, HTTP and HTTPS origins are different.
An origin is a combination of hostname, port, and scheme.
http://foo.example.com:8080/
^^^^ ^^^^^^^^^^^^^^^ ^^^^
|| || ||
scheme hostname port
So if I run this test I get the CORS error
it('throws a CORS error', () => {
const domain1 = 'https://localhost:3003'
const domain2 = 'http://localhost:3003'
cy.intercept(domain1, '<html></html>').as('lh') // stubbing because no server running
cy.visit(domain1)
cy.wait('#lh')
cy.window().then(win => {
win.location.href = domain2 // Cypress detected a cross origin error happened...
})
})
In your test, there could be a couple of reasons
normally localhost uses http:// not https://. If you try it the browser give you This site can’t provide a secure connection. May be Cypress is correcting the scheme when it sees hostname localhost.
Your main page is using scheme http:// but your redirect is navigating to https://.

Google Cloud App Engine gives Cannot Get / for the first request

I have a perfectly fine express node.js app that serves http requests. I put it on Google Cloud App Engine.
It serves the pages fine, but occasionally when I visit the page I get a white page that says:
Cannot Get /
if I refresh the page is served as usual.
This happens very often for my initial requests for the website. I use a custom domain.
How can I track this error, it doesn't show up in the log viewer. What is the cause of this?
app.yaml
runtime: nodejs12
env_variables:
BUCKET_NAME: "example-gcs-bucket"
handlers:
- url: /.*
secure: always
script: auto
Thanks for sharing additional information on the comments, we could discard any issues with your app.yaml file.
It seems that App Engine fails from time to time to add the CSP headers in their responses, making the message Content Security Policy of your site blocks some resources because their origin is not included in the content security policy header to be shown as found by John Hanley.
After some deep research it seems that this is a known issue and the GCP team is working to fix this. To mitigate this in the meantime you will need to set a policy as a HTTP header (recommended), or via an HTML <meta> tag following this Public blog.
I have created this Issue Tracker for you and the community to track the GCP team's progress. It includes the steps to diagnose the issue and the current workaround

Google Tag Manager 403's every request even if CORS mapping is defined

when I moved to AMP, the Google Tag Manager stopped to working.
The problem occurs every time when I open my AMPed page, I can see some errors in browser console, e.g.
First error:
https://www.googletagmanager.com/amp.json?id=MY_GTM_TAG&gtm.url=MY_HTTP_URL
(403)
Second error:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '' is therefore not allowed access. The response had HTTP status code 403. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
In my class that extends WebMvcConfigurerAdapter I overwritten the method addCorsMappings like this:
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowCredentials(true);
};
But it still doesn't work (this method is executed on startup, I checked it). Do you have any ideas / tips why?
EDIT 1 (22.12.2016):
Q: How are you loading tag manager? Are you using the AMP version of the script? (#Jim Jeffries)
A: Yes, in <head> I included the following piece of code:
<script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
and in <body> there is:
<amp-analytics config="https://www.googletagmanager.com/amp.json?id=${googleTagId}&gtm.url=SOURCE_URL" data-credentials="include"></amp-analytics>
I was having the same issue and it turns out you can't use your old GTM "Web" container for this so you'll have to create a specific AMP Container.
As per Google's instructions found here:
Create an AMP container
Tag Manager features an AMP container type. Create a new AMP container for your project:
On the Accounts screen, click More Actions (More) for the account
you'd like to use. Select Create Container.
Name the container. Use a descriptive name, e.g. "example.com - news - AMP".
Under "Where to Use Container", select AMP.
Click "Create".
Based from this thread, maybe you are doing an XMLHttpRequest to a different domain than your page is on. So the browser is blocking it as it usually allows a request in the same origin for security reasons. You need to do something different when you want to do a cross-domain request. A tutorial about how to achieve that is Using CORS.
*When you are using postman they are not restricted by this policy. Quoted from Cross-Origin XMLHttpRequest:*
Regular web pages can use the XMLHttpRequest object to send and receive data from remote servers, but they're limited by the same origin policy. Extensions aren't so limited. An extension can talk to remote servers outside of its origin, as long as it first requests cross-origin permissions.
Also based from this forum, the app must authenticate as a full admin and POST the desired CORS configuration to /rest/system/config.

CORS authenticated requests to Google Sites feed/API blocked

I'm currently building an ASP.NET web application to simplify the provisioning of Google Sites, pages, Gadgets on Google Sites and ACLs for Google Sites.
I have encountered the issue which many a developer has already come across: cross-origin resources. According to the Google documentation on CORS requests to Google APIs, you simply use an XMLHttpRequest (or AJAX) request, providing your access token in the header. More information can be found here:
https://developers.google.com/api-client-library/javascript/features/cors
I've been perfectly able to accomplish this when I'm accessing the Google Sites API from within my domain on Google Sites, injecting AJAX requests while my browser window's location is within the domain. An example of a succeeded request to make a new site from within my domain:
$.ajax(
{
////// REQUEST \\\\\\
type: "POST",
url: "https://sites.google.com/feeds/site/[domainName]",
contentType: "application/atom+xml",
headers: {
"Authorization": "Bearer " + [accessToken],
"GData-Version": "1.4"
},
data: ["<entry xmlns='http://www.w3.org/2005/Atom' xmlns:sites='http://schemas.google.com/sites/2008'>",
"<title>What a site</title>",
"<summary>Best description ever.</summary>",
"<sites:theme>ski</sites:theme>",
"</entry>"].join(""),
////// LOGGING \\\\\\
beforeSend: function () {
console.log('-------Making-the-request-------');
},
success: function (result) {
console.log(result);
},
error: function (xhr, ajaxOptions, thrownError) {
console.log(thrownError);
console.log(xhr.status);
}
});
(In several cases below, I'm writing https:// as [https] due to my account still being restricted to 2 links in a post).
At this point everything was going great, I thought I had everything set to use the code into my ASP.NET site. Alas, things don't always go to plan. When I executed the exact same AJAX call from within my application (right now still hosted on [https]localhost:44301), I get the following error:
XMLHttpRequest cannot load
[https]sites.google.com/feeds/site/[censored] Response to preflight
request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin '[https]localhost:44301' is therefore not allowed
access. The response had HTTP status code 405.
The usual CORS error. I was surprised though, as the advised way of making requests to Google APIs is exactly that. I've also found an article about using CORS with the Google Cloud API:
https://cloud.google.com/storage/docs/cross-origin
In the article it states:
Most clients (such as browsers) use the XMLHttpRequest object to make
a cross-domain request. XMLHttpRequest takes care of all the work of
inserting the right headers and handling the CORS interaction with the
server. This means you don't add any new code to take advantage of
CORS support, it will simply work as expected for Google Cloud Storage
buckets configured for CORS.
Of course, this isn't the Google Sites API, but I find it hard to believe that Google hasn't implemented the same functionality in all of their APIs.
Does anyone know whether it's possible to achieve successful requests such as this from within a standalone ASP.NET application? And if so, how?
Many thanks for spending time to read about my hardships.
UPDATE:
I've contacted Google Apps Support regarding my issue, and have gotten the following response:
In addition to the information you provided, I also reviewed your post
at
CORS authenticated requests to Google Sites feed/API blocked.
The note at
https://developers.google.com/google-apps/sites/docs/1.0/developers_guide_java#SiteFeedPOST
only reinforces the statement under 'Can I create a new Google Site?'
and 'How do I copy a site?' at
https://developers.google.com/google-apps/sites/faq#Getting__Started,
which states 'Google Apps users can use the site feed to ...' However,
I don't see why this is relevant to your issue, if you've authorised
against your domain administrator account, as the note is only
indicating that gmail.com users won't be able to use the listed
methods to create, or copy a site.
I haven't used CORS, so can't comment on it's operation, but have been
able to successfully list, and create sites using HTTP GET and POST
requests via a raw HTTP client, so the API is operating as it should
with regard to cross domain requests. I used the sample XML document
at
https://developers.google.com/google-apps/sites/docs/1.0/developers_guide_protocol#SitesFeedPOST
to create my site, configuring the client with credentials for my
Developer console project. The fact that the request only fails in
your ASP.NET site implies that there is something in that environment
which isn't configured correctly. Unfortunately that's outside my
scope of support, so I'm unable to provide any specific advice, other
than to check the relevant documentation, or post a request in the
ASP.NET section of Stack Overflow at
https://stackoverflow.com/questions/tagged/asp.net.
Could you try again after removing "GData-Version": "1.4"?
If you really want to send this value, send it by adding a query parameter such as v=X.0. Resource from here
UPDATED:
"Note: This feature is only available to Google Apps domains." From guides
SOLVED
It seems that a lot of browsers would still block an AJAX request across different domains, even when it's allowed by the API you're trying to reach. Instead of using AJAX, I'm now using the C# WebRequest in a DLL.
Example:
// Create the request
WebRequest request = WebRequest.Create("https://sites.google.com/feeds/site/[domainName]");
request.Method = "POST";
request.contentType = "application/atom+xml";
request.Headers.Set(HttpRequestHeader.Authorization, "Bearer " + [accessToken]);
request.Headers["GData-Version"] = "1.4";
// Fill in the data and encode for the datastream
string data = ["<entry xmlns='http://www.w3.org/2005/Atom' xmlns:sites='http://schemas.google.com/sites/2008'>",
"<title>What a site</title>",
"<summary>Best description ever.</summary>",
"<sites:theme>ski</sites:theme>",
"</entry>"].join("");
byte[] byteArray = Encoding.UTF8.GetBytes (data);
// Add the data to the request
Stream dataStream = request.GetRequestStream ();
dataStream.Write (byteArray, 0, byteArray.Length);
dataStream.Close ();
// Make the request and get the response
WebResponse response = request.GetResponse ();
More info can be found on MSDN:
https://msdn.microsoft.com/en-us/library/debx8sh9(v=vs.110).aspx

Resources