Google Cloud Functions enable CORS? - google-cloud-platform

I just finished the Hello World Google Cloud Functions tutorial and received the following response headers:
Connection → keep-alive
Content-Length → 14
Content-Type → text/plain; charset=utf-8
Date → Mon, 29 Feb 2016 07:02:37 GMT
Execution-Id → XbT-WC9lXKL-0
Server → nginx
How can I add the CORS headers to be able to call my function from my website?

here we go:
exports.helloWorld = function helloWorld(req, res) {
res.set('Access-Control-Allow-Origin', "*")
res.set('Access-Control-Allow-Methods', 'GET, POST');
if (req.method === "OPTIONS") {
// stop preflight requests here
res.status(204).send('');
return;
}
// handle full requests
res.status(200).send('weeee!);
};
then you can jquery/whatever it as usual:
$.get(myUrl, (r) => console.log(r))

I'm the product manager for Google Cloud Functions. Thanks for your question, this has been a popular request.
We don't have anything to announce just yet, but we're aware of several enhancements that need to be made to the HTTP invocation capabilities of Cloud Functions and we'll be rolling out improvements to this and many other areas in future iterations.
UPDATE:
We've improved the way you deal with HTTP in Cloud Functions. You now have full access to the HTTP Request/Response objects so you can set the appropriate CORS headers and respond to pre-flight OPTIONS requests (https://cloud.google.com/functions/docs/writing/http)
UPDATE (2022):
Just noticed there was a question about docs, and our docs have moved. Updated docs for CORS are here:
https://cloud.google.com/functions/docs/samples/functions-http-cors

You can use the CORS express middleware.
package.json
npm install express --save
npm install cors --save
index.js
'use strict';
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors')({origin: true});
const app = express();
app.use(cors);
app.get('*', (req, res) => {
res.send(`Hello, world`);
});
exports.hello = functions.https.onRequest(app);

I've just created webfunc. It's a lightweight HTTP server that supports CORS as well as routing for Google Cloud Functions. Example:
const { serveHttp, app } = require('webfunc')
exports.yourapp = serveHttp([
app.get('/', (req, res) => res.status(200).send('Hello World')),
app.get('/users/{userId}', (req, res, params) => res.status(200).send(`Hello user ${params.userId}`)),
app.get('/users/{userId}/document/{docName}', (req, res, params) => res.status(200).send(`Hello user ${params.userId}. I like your document ${params.docName}`)),
])
In your project's root, simply add a appconfig.json that looks like this:
{
"headers": {
"Access-Control-Allow-Methods": "GET, HEAD, OPTIONS, POST",
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
"Access-Control-Allow-Origin": "*",
"Access-Control-Max-Age": "1296000"
}
}
Hope this helps.

In the python environment, you can use the flask request object to manage CORS requests.
def cors_enabled_function(request):
if request.method == 'OPTIONS':
# Allows GET requests from any origin with the Content-Type
# header and caches preflight response for an 3600s
headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '3600'
}
return ('', 204, headers)
# Set CORS headers for the main request
headers = {
'Access-Control-Allow-Origin': '*'
}
return ('Hello World!', 200, headers)
See the gcloud docs for more.

You need to send an 'OPTIONS' response by setting its header as follows:
if (req.method === 'OPTIONS') {
res.set('Access-Control-Allow-Methods', '*');
res.set('Access-Control-Allow-Headers', '*');
res.status(204).send('');
}
Runtime: NodeJS 10

If you tried the accepted answer but encountered a preflight error, the docs offer examples of handling it in multiple languages, with the caveat that it only works on public functions, i.e. deployed with --allow-unauthenticated:
exports.corsEnabledFunction = (req, res) => {
res.set("Access-Control-Allow-Origin", "*");
if (req.method === "OPTIONS") {
/* handle preflight OPTIONS request */
res.set("Access-Control-Allow-Methods", "GET, POST");
res.set("Access-Control-Allow-Headers", "Content-Type");
// cache preflight response for 3600 sec
res.set("Access-Control-Max-Age", "3600");
return res.sendStatus(204);
}
// handle the main request
res.send("main response");
};
Another option is to use Express as shown in this post, complete with cross-origin enabled.

You must enable CORS within all your functions, for example hello function:
index.js
const cors = require('cors')();
// My Hello Function
function hello(req, res) {
res.status(200)
.send('Hello, Functions');
};
// CORS and Cloud Functions export
exports.hello = (req, res) => {
cors(req, res, () => {
hello(req, res);
});
}
Don't forget about package.json
package.json
{
"name": "function-hello",
"version": "0.1.0",
"private": true,
"dependencies": {
"cors": "^2.8.5"
}
}

After applying your favourite answer from here, if you're still getting this error, check for uncaught errors in your cloud function. This can result in the browser receiving a CORS error, even when your error has nothing to do with CORS

After CORS enabled if you send POST request to your function also check for your request Content-Type header, mine was set it to "text/plain" and my browser was constantly triggering CORS errors, after setting the header to "application/json" everything worked properly.

Related

fetch() Not setting cookie despite `Set-Cookie` header

My backend-api written in Node and Express.js sets a cookie using res.cookie:
Router.post('/login', async (req, res) => {
const email = req.body.email;
const password = req.body.password;
try {
let result = await sqlite.login(email, password);
res.cookie('token', result, {
'maxAge': 3600 * 1000
});
res.send({
'token' : result
});
} catch (err) {
res.send(err);
}
});
I can make a request to this route, and I do notice the Set-Cookie header is set on the response object within Chrome developer tools:
Set-Cookie: token=[...]; Max-Age=3600; Path=/; Expires=Mon, 11 Jul 2022 14:47:08 GMT
However, document.cookie is never set by the browser. From my searching, most people say to specify the credentials field as same-origin. I have done this and it made no change. My cookie is NOT being set as HttpOnly, so I am unsure why it's being set by the browser.
Here is where I call the /login route:
async login(email, password) {
let response = await fetch(apiURL + '/login', {
'method' : 'POST',
'headers' : {
'Content-Type' : 'application/json',
'Accept' : 'application/json'
},
'credentials' : 'same-origin',
'body' : JSON.stringify({
'email' : email,
'password' : password
})
});
return await response.json();
}
A token is successfully returned in the response, but again document.cookie returns an empty string ''.
From searching this problem, most of the issues seem to suggest that same-origin should fix the issue but it is not the case for myself. Another thing of note is that httpOnly cookies won't show in the browser, but I know that the cookies I am sending are not HttpOnly.
I am using Google Chrome version 103.0.5060.114.
If I set credentials to include, I get a CORS error:
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
I was able to get it working with the following changes:
When initializing npm package cors, specify some options:
app.use(cors({ credentials: true, origin: 'http://lvh.me:3001' }));
The origin must include the http:// prefix as well as the correct port. Once that was done, set credentials to include when calling fetch() and it should work fine.

How to call a server from localhost VueJS with Axios?

I'm trying to call an AWS hosted API from my VueJS app, which is running on my localhost:8080. I have used this blog post to setup the vue.config.js with this block:
module.exports = {
devServer: {
proxy: 'https://0123456789.execute-api.eu-west-1.amazonaws.com/'
},
...
}
With this in place, I can use this code to make a GET request to an endpoint at that host:
this.$axios
.get('https://0123456789.execute-api.eu-west-1.amazonaws.com/mock/api/endpoint',
{
headers: {
'Content-Type': 'application/json'
}})
This is because I have configured the AWS API Gateway mock endpoint to return these headers for the OPTIONS method:
Access-Control-Allow-Headers: 'Cache-Control,Expires,Pragma,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'
Access-Control-Allow-Methods: 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'
Access-Control-Allow-Origin: '*'
However, I cannot make this call:
this.$axios
.get('https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function',
{
headers: {
'Content-Type': 'application/json'
}})
This endpoint is a Lambda integration and also has an OPTIONS method with the same headers as above.
Why should both endpoints, configured the same way, have different responses for axios?
UPDATE
As advised by #deniz, I have updated the .env.development file to contain:
VUE_APP_API_URI=https://0123456789.execute-api.eu-west-1.amazonaws.com/
I have also updated the axios requests to:
let url = 'mock/api/endpoint'
let headers = {
headers: {
'Content-Type': 'application/json',
},
}
this.$axios
.get(url, headers)
...and...
let url = 'lambda/api/function'
let headers = {
headers: {
'Content-Type': 'application/json',
},
}
this.$axios
.get(url, headers)
The result I get for the first GET request is:
200 OK
However the second request's response is:
Access to XMLHttpRequest at 'https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Your config for your dev env. as a proxy setup is doing nothing else then pretend to be someone else.
Thats why you dont get any CORS issues when you work with a proxy. its a kinda bottleneck which acts like "i am someone else, not localhost"
module.exports = {
devServer: {
proxy: 'https://0123456789.execute-api.eu-west-1.amazonaws.com/'
},
...
}
from now on all your requests came from this very proxy based URL
https://0123456789.execute-api.eu-west-1.amazonaws.com/
if you try to access the api like this:
this.$axios
.get('https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function',
{
headers: {
'Content-Type': 'application/json'
}})
you should keep in mind that you are already pretend that your proxy is doing his desguise stuff and still acts like its from a other source.
your URL when you call the API looks like this now, if i am not completely wrong:
https://0123456789.execute-api.eu-west-1.amazonaws.com/https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function
all you have to do is change the axios url in your request to:
this.$axios
.get('lambda/api/function',
{
headers: {
'Content-Type': 'application/json'
}})
and try again.
UPDATE
VUE_APP_API_URI=https://0123456789.execute-api.eu-west-1.amazonaws.com/
wrap your URL string into quotes, like this and remove the last slash.
VUE_APP_API_URI='https://0123456789.execute-api.eu-west-1.amazonaws.com'
thats a common practice to handle .env vars.
2.
the CORS error you get is a result of not using proxy anymore.
your requesting data from a other source now and this is no allowed on modern browsers like FireFox or Chrome etc.
here you have to handle the server side configs in your API:
https://0123456789.execute-api.eu-west-1.amazonaws.com
because if you go like that you need to give your localhost and your backend the permission to handle requests if the requests are made from different sources, like in your case:
i am localhost and i request data from https://0123456789.execute-api.eu-west-1.amazonaws.com
normally this is forbidden and is a highly risk on security
But the solution is...
As you did before in your AWS API
Access-Control-Allow-Origin: '*' is the important part which handles your "CORS" issues.
make sure it is setup correct and works as intended. maybe play around with that and set localhost instead of * (allow for all)
3.
i highly recommend you to use the proxy way on development and use the non proxy way only for production, and just allow CORS for your frontend only.

Why is AWS Lambda, API Gateway returning a CORS error

I know this issue has been covered in many posts all over the web and I think I've tried them all, but I'm still getting a 403 CORS error to my local react app.
Here are in part, the Headers from Dev Tools:
#GENERAL:
Request URL: https://<myGatewayApiUrl>.amazonaws.com/dev/api/byid/1/129
Request Method: OPTIONS
Status Code: 403
#RESPONSE HEADERS
access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token
access-control-allow-methods: GET,OPTIONS
access-control-allow-origin: *
content-length: 42
content-type: application/json
I've been working in the API Gateway setting the Enable CORS, but I get an error for one get methods Add Access-Control-Allow-Origin Integration Response Header Mapping to GET method -> invalid response status code specified - But the OPTIONS headers get set and the GET header Access-Control-Allow-Origin is set.
I am using express and cors packages, here's a snippet from my API index.js file:
const app = express();
app.use(cors());
app.options('*', cors());
Here is the request code from React app:
export const getRecordById = async (userId, id, token) => {
try {
const response = await axios.get(
process.env.REACT_APP_API_URL + `/byid/${userId}/${id}`,
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
return response.data;
} catch (error) {
console.log('ERROR', error);
return error;
}
};
Here is my response code from the Lambda API:
getById: asyncHandler(async (req, res, next) => {
const { user, id } = req.params;
const result = await recordsService.getRecordById(user, id);
res.set({
'content-type': 'application/json',
'Access-Control-Allow-Origin': '*',
});
if (!result) {
res.status(400).json({
error: true,
message: 'get record by ID action failed',
data: {},
});
}
res.status(200).json({
error: false,
message: 'successful record retrieval',
data: {
record: result,
},
});
}),
Also, I have my serverless.yml file http events set as such: (from what I understand cors: true should handle the preflight requests)
- http:
path: /api/records/byid/{user}/{id}
method: GET
cors: true
I've spent way too much time trying to figure this out. It must be something simple and dumb, am I using res.set() properly? Everything looks correct, I know I'm missing something. Thanks
API Gateway will reject the call with a CORS error when a URL is not found by default.
It looks like Axios is missing the /records bit from the request URL.

Express lambda return JSON from api call

I have a serverless express app. In the app I have a app.get called '/', which should call an api, retrieve the data from the api and send it back to the user.
https://y31q4zn654.execute-api.eu-west-1.amazonaws.com/dev
I can see data as json on the page being returned.
This is my index.js of the lambda function:
const serverless = require('serverless-http');
const express = require('express');
const request = require('request');
const app = express()
app.get('/', function (req, res) {
var options = { method: 'POST',
url: 'https://some.api.domain/getTopNstc',
headers:
{ 'Content-Type': 'application/json' },
body: {},
json: true
};
request(options, function (error, response, body) {
console.log('request call')
if (error) throw new Error(error);
// res.status(200).send(response);
res.json(response);
});
});
module.exports.handler = serverless(app);
However I would to be able to call the lambda '/' via axios (or other promise-request library)
I've tried to use the following code to make a call to my lambda:
axios.get('https://y31q4zn654.execute-api.eu-west-1.amazonaws.com/dev', {
headers: {
'Content-Type': 'application/json',
},
body:{}
}).then((res) => {
console.log(res);
});
Failed to load
https://y31q4zn654.execute-api.eu-west-1.amazonaws.com/dev: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'myDomain' is therefore not
allowed access. bundle.js:31 Cross-Origin Read Blocking (CORB) blocked
cross-origin response
https://y31q4zn654.execute-api.eu-west-1.amazonaws.com/dev with MIME
type application/json. See
https://www.chromestatus.com/feature/5629709824032768 for more
details.
Api gateway config:
I concur with #KMo. Pretty sure this is a CORS issue. There is a module in npm exactly for this purpose, read up about it here.
To install it, run npm install -s cors
Then in your express app, add the following:
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors());

Fetch API with Cookie

I am trying out the new Fetch API but is having trouble with Cookies. Specifically, after a successful login, there is a Cookie header in future requests, but Fetch seems to ignore that headers, and all my requests made with Fetch is unauthorized.
Is it because Fetch is still not ready or Fetch does not work with Cookies?
I build my app with Webpack. I also use Fetch in React Native, which does not have the same issue.
Fetch does not use cookie by default. To enable cookie, do this:
fetch(url, {
credentials: "same-origin"
}).then(...).catch(...);
In addition to #Khanetor's answer, for those who are working with cross-origin requests: credentials: 'include'
Sample JSON fetch request:
fetch(url, {
method: 'GET',
credentials: 'include'
})
.then((response) => response.json())
.then((json) => {
console.log('Gotcha');
}).catch((err) => {
console.log(err);
});
https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
Have just solved. Just two f. days of brutforce
For me the secret was in following:
I called POST /api/auth and see that cookies were successfully received.
Then calling GET /api/users/ with credentials: 'include' and got 401 unauth, because of no cookies were sent with the request.
The KEY is to set credentials: 'include' for the first /api/auth call too.
If you are reading this in 2019, credentials: "same-origin" is the default value.
fetch(url).then
Programmatically overwriting Cookie header in browser side won't work.
In fetch documentation, Note that some names are forbidden. is mentioned. And Cookie happens to be one of the forbidden header names, which cannot be modified programmatically. Take the following code for example:
Executed in the Chrome DevTools console of page https://httpbin.org/, Cookie: 'xxx=yyy' will be ignored, and the browser will always send the value of document.cookie as the cookie if there is one.
If executed on a different origin, no cookie is sent.
fetch('https://httpbin.org/cookies', {
headers: {
Cookie: 'xxx=yyy'
}
}).then(response => response.json())
.then(data => console.log(JSON.stringify(data, null, 2)));
P.S. You can create a sample cookie foo=bar by opening https://httpbin.org/cookies/set/foo/bar in the chrome browser.
See Forbidden header name for details.
Just adding to the correct answers here for .net webapi2 users.
If you are using cors because your client site is served from a different address as your webapi then you need to also include SupportsCredentials=true on the server side configuration.
// Access-Control-Allow-Origin
// https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api
var cors = new EnableCorsAttribute(Settings.CORSSites,"*", "*");
cors.SupportsCredentials = true;
config.EnableCors(cors);
This works for me:
import Cookies from 'universal-cookie';
const cookies = new Cookies();
function headers(set_cookie=false) {
let headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
};
if (set_cookie) {
headers['Authorization'] = "Bearer " + cookies.get('remember_user_token');
}
return headers;
}
Then build your call:
export function fetchTests(user_id) {
return function (dispatch) {
let data = {
method: 'POST',
credentials: 'same-origin',
mode: 'same-origin',
body: JSON.stringify({
user_id: user_id
}),
headers: headers(true)
};
return fetch('/api/v1/tests/listing/', data)
.then(response => response.json())
.then(json => dispatch(receiveTests(json)));
};
}
My issue was my cookie was set on a specific URL path (e.g., /auth), but I was fetching to a different path. I needed to set my cookie's path to /.
If it still doesn't work for you after fixing the credentials.
I also was using the :
credentials: "same-origin"
and it used to work, then it didn't anymore suddenly, after digging much I realized that I had change my website url to http://192.168.1.100 to test it in LAN, and that was the url which was being used to send the request, even though I was on http://localhost:3000.
So in conclusion, be sure that the domain of the page matches the domain of the fetch url.