I'm trying to use Cloud Functions for Firebase to serve content and I'd like to detect whether a user is logged in or not. I've set up a rewrite in my firebase.json that looks like this:
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "public",
"rewrites": [{
"source": "**",
"function": "getProfile"
}]
}
}
This works fine and I'm serving appropriate content based on the path that's being requested. However, because I'm not doing anything on the client side (i.e. I'm using rewrites rather than client-side redirects), I'm missing the opportunity to get the current user from a client-side script.
Is there some way I can use a header or a property of the request object so that I can serve different content to logged in vs. non-logged in users in my server-side rewrites scenario?
Firebase Hosting passes along any cookie named __session when it calls a Cloud Function. An easy way to do this is to simply listen for ID tokens in your web app and set the cookie appropriately:
firebase.auth().onIdTokenChanged(user => {
if (user) {
user.getIdToken().then(token => {
document.cookie = `__session=${token};max-age=3600`;
});
} else {
document.cookie = '__session=;max-age=0';
}
});
Then, in your Cloud Function, you can parse the ID token out of the cookie and verify it using code like in this sample.
Related
I have two cloud functions, youtubeRedirect and youtubeToken. Before doing the URL rewrite, (as found here https://firebase.google.com/docs/hosting/functions), everything was working.
youtubeRedirect takes the user to a google auth screen, and provides a redirect to the youtubeToken function. It sets these two cookies
res.cookie('state', state.toString(), { maxAge: 3600000, secure: true, httpOnly: true });
res.cookie('UID', req.query.uid);
When the user is redirect to youtubeToken, the cookie value is checked;
if (!req.cookies.state) {throw new Error('State cookie not set or expired.');}
When using URL rewrites for my custom domain, it fails at this point as req.cookies is empty. When I check my cookies in Chrome, I can see the cookies have been set for my custom domain. I have rewritten both youtubeRedirect and youtubeToken in my firebase.json file
"hosting": {
"public": "api",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "/youtubeRedirect",
"function": "youtubeRedirect"
},
{
"source": "/youtubeToken",
"function": "youtubeToken"
}
]
}
The auth side of things is working fine - authorised redirect URI's etc. all changed beforehand, where it used to redirect to us-central1-[appName].cloudfunctions.net it now redirects to my custom domain. The redirection to youtubeToken is working - my brower URL is showing my custom domain.
If I revert everything back to using the us-central1-[appName].cloudfunctions.net function address, it all works. Checking the cookies in devtools shows that the cookies are set as I expect. If I run it all with my custom domain, the cookies are also there... except the function itself cannot find them.
It seems to be that the cookies are being checked somehow before the URL rewrite has occurred, and failing? Really struggling to see a solution to this!
I thought I had found a smoking gun when having console.log(req.get('host')) in youtubeToken returned us-central1-[appName].cloudfunctions.net however placing it within youtubeRedirect has the same outcome, so that just confused me further...
Found the answer in another question
When using Firebase Hosting together with Cloud Functions or Cloud Run, cookies are generally stripped from incoming requests. This is necessary to allow for efficient CDN cache behavior. Only the specially-named __session cookie is permitted to pass through to the execution of your app.
Source
My solution is to set the cookie like so in youtubeRedirect
const sessionCookie = {
state: state.toString(),
UID: req.query.uid
}
res.cookie('__session', JSON.stringify(sessionCookie), { maxAge: 3600000, secure: true, httpOnly: true });
And then retrieve it in youtubeToken
const sessionCookie = JSON.parse(req.cookies.__session)
if (!sessionCookie.state) {
I need to execute a Firebase function AND load a page using a single URL.
e.g.
URL = "localhost:5000/cars?make=hyundai&model=elantra"
This URL will load the views/cars/index.html webpage.
This URL will also call the loadCar() Firebase function.
...How can I achieve these two goals? (or is there a better way to achieve this - load a page AND invoke a function)
Current behaviour: It loads the page (achieve 1), but doesn't invoke the loadCar() function (doesn't achieve 2).
FYI my firebase.json:
{
"hosting": {
"public": "functions/views",
"rewrites": [
{
"source": "/cars/**",
"function": "loadCar"
}
]
}
My file directory (arrows pointing to relevant files):
loadCar():
exports.loadCar= functions.https.onRequest((req, res) => {
// const requestedCar = Get requested car info from URL queries.
// Save requestedCar to Firestore DB, so it can be loaded in the html view.
// ... etc.
res.end();
});
If you're asking if it's possible for a single Firebase Hosting URL to both load static content and trigger a function, it's not possible. It has to be either one, not both.
You could instead have some JavaScript code in the static content invoke the function through another URL. Or you could have the URL invoke the function, which returns HTML to display, in addition to performing other work. But the destination of the requested URL can only go to static content or a function, not both.
I am using firebase hosting to host few scripts and trying to access them from another site. it naturally gets blocked due to CORS issues. based on my research on other forum threads etc i modified the firebase.json as below
{
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"headers": [ {
"source" : "**",
"headers" : [ {
"key" : "Access-Control-Allow-Origin",
"value" : "*"
} ]
}]
}
}
which essentially allow any url to access the resources hosted here. however, on trying to run my site i still see below
Access to XMLHttpRequest at 'https://oracle-bot-sdk.firebaseapp.com//loader.json'
from origin 'https://insurance-bot.moblize.it' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
what else is needed?
In addition to your firebase.json changes for cors, your firebase functions http / https function needs to also include the cors plugin.
Example
const cors = require('cors')({origin: true});
const functions = require('firebase-functions');
const app = functions.https.onRequest((req, res) => {
cors(req, res, () => {
// Your app stuff here
// Send Response
res.status(200).send(<response data>);
});
});
Express App Example
import express from "express";
const cors = require('cors')({origin: true});
const app = express();
app.get('**', (req, res) => {
cors(req, res, () => {
// Your App Here
// Send response
res.status(200).send(<response data>);
});
});
More documentation Serve Dynamic Content with Cloud Functions - Create an HTTP function to your Hosting site (Cors is not mentioned in the documentation btw)
Is the site (https://insurance-bot.moblize.it/) that is calling to https://oracle-bot-sdk.firebaseapp.com a Firebase hosted app?
I only ask because with version 4.2+ of Firebase Tools allows you to setup Multisite hosting using the same Firebase Project. I am not sure if that would help your situation out at all. Just wanted to mention it.
In the error message:
insurance-bot.moblize.it/:1 Failed to load https://oracle-bot-sdk.firebaseapp.com//loader.json: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://insurance-bot.moblize.it' is therefore not allowed access.
I noticed an extra '/' in https://oracle-bot-sdk.firebaseapp.com//loader.json. I doubt that is the issue, but wanted to mention it.
There is something that you could try. Similar to the answers above but a little different:
"headers": [
{
"source": "*",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
]
Also I would read some of the info here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Access-Control-Allow-Origin If you have not already.
I hope I was able to help in some way. Let me know.
My guess that you've mixed up firebase hosting and firebase cloud functions. Firebase hosting is made for hosting static websites and web apps. As you try to access from your website that is hosted on different domain your configuration for hosting is not applied. You mentioned that you host some scripts and it sounds like cloud functions. And good old CORS headers can help to your cloud functions like:
exports.corsEnabledFunction = (req, res) => {
res.set("Access-Control-Allow-Origin", "*");
res.set("Access-Control-Allow-Methods", "GET");
res.set("Access-Control-Allow-Headers", "Content-Type");
res.set("Access-Control-Max-Age", "3600");
// Continue with function code
...
}
More info: https://cloud.google.com/functions/docs/writing/http#handling_cors_requests
Make sure you have the Blaze or Flame plan, I think Spark plan blocks external access, maybe for the same reason as it does with cloud functions
Cloud Functions for Firebase - Billing account not configured
Go to the Google Cloud Console: https://console.cloud.google.com/functions/
Click the checkbox next to the function on which you want to grant access.
Click Show Info Panel in the top right corner to show the Permissions tab.
Click Add member.
In the New members field, type allUsers.
Select the role Cloud Functions > Cloud Functions Invoker from the Select a role drop-down menu.
Click Save.
taken from: https://github.com/firebase/firebase-functions/issues/645#issuecomment-605835353
This was the best solution for me as posted above
Go to the Google Cloud Console: https://console.cloud.google.com/functions/
Click the checkbox next to the function on which you want to grant access.
Click Show Info Panel in the top right corner to show the Permissions tab.
Click Add member.
In the New members field, type allUsers.
Select the role Cloud Functions > Cloud Functions Invoker from the Select a role drop-down menu.
Click Save.
taken from: https://github.com/firebase/firebase-functions/issues/645#issuecomment-605835353
Try pasting this as it's directly from the documentation, Customize Hosting Behavior:
"hosting": {
// Add the "headers" section within "hosting".
"headers": [ {
"source" : "**/*.#(eot|otf|ttf|ttc|woff|font.css)",
"headers" : [ {
"key" : "Access-Control-Allow-Origin",
"value" : "*"
} ]
}
}
Firebase hosting CORS doesn't work WITH custom domain.
However, CORS API works with https://yyyyyyy.web.app/ or firebaseapp.com domain
I built a small web application (www.suntax.de) with vuejs and hosted on Google Firebase. I use the Firebase Hosting, Database and Functions. I use Functions to do some calculation on the backend.
Everything is running now, but I had some trouble getting there and some things I would like to improve regarding my workflow.
Thus, I would like to explain you what I did and where I struggle now.
First I set up the vuejs applicationd deployed it to firebase. I added my custom domain and everything was doing fine.
Later I implemented the cloud function and want to make a call from my vuejs app.
After firebase deploy, I can call this function in the browser and it just works fine:
https://us-central1-suntax-6d0ea.cloudfunctions.net/calcEEGcompensation?year=2013&month=1&size=10
Now I thought, that I just call the URL of the function from my vuejs app. But then I got the following error message:
[Error] Origin * is not allowed by Access-Control-Allow-Origin.
I was then reading that I had to add a rewritesection in the firebase.json:
Now my firebase.json looks like this:
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
// Add the following rewrites section *within* "hosting"
"rewrites": [ {
"source": "/calcEEGcompensation", "function": "calcEEGcompensation"
} ]
}
}
Now I was able to call my firebase function with the following URL:
https://www.suntax.de/calcEEGcompensation?year=2013&month=1&size=10
After integrating the above URL in my vuejs application, the application is running fine after deployment to firebase server.
As I want to keep improving the application, I would like to test everything locally before deploying.
I know that I can run firebase hosting and functions locally by:
firebase serve --only functions,hosting
However, now my application has the hard coded call to my function https://www.suntax.de/calcEEGcompensation?year=2013&month=1&size=10 and this again leads to the error [Error] Origin * is not allowed by Access-Control-Allow-Origin.
But also changing the URL to the local function
http://localhost:5001/suntax-6d0ea/us-central1/calcEEGcompensation?year=2013&month=1&size=10
leads to the error message
[Error] Origin * is not allowed by Access-Control-Allow-Origin.
Some further reading brought me to the solution with cors. So I changed my function to:
const functions = require('firebase-functions');
const cors = require('cors')({origin: true});
exports.calcEEGcompensation = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const yearOfCommissioning = req.query.year;
const monthOfCommissioning = req.query.month;
const pvsystemsize = req.query.size;
...
res.send("hello");
});
});
This helped and everything works now:
- The deployed application is still running fine.
- I can run the application locally while calling the local function as well as the deployed function. I just have to change the URL of the function.
But this is now my question:
Can I solve this issue in a better way? If I test the vuejs application and the function locally, I have to change the function URL before deployment. And then I have to change it back while testing locally.
I was not able to test my application and function locally without cors.
My ideal solution would be to have a setup, that can be fully tested locally and which can be easily deployed with firebase deploy without any changes of URLs. Is this possible?
Thanks and best regards,
Christoph
I found the solution which is pretty simple and does exactly what I want. I have no idea why I did not figure it out before.
Here is what I did:
I just call the relative URL from my firebase hosting:
calcEEGcompensation?year=2013&month=1&size=10
and everything works fine if the rewrites are properly set in firebase.json:
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
// Add the following rewrites section *within* "hosting"
"rewrites": [ {
"source": "/calcEEGcompensation", "function": "calcEEGcompensation"
} ]
}
}
After setting up everything like this, I can just execute firebase serve --only functions,hosting and I can test everything locally.
After executing firebase deploy, everything runs smoothly on the server.
I do not need cors.
Thanks #wonsuc for your answers.
Update(For Firebase Hosting):
Currently there is no workaround you can solve it with Firebase Hosting SDK.
But there is alternative way you can achieve this.
Try below code in your hosting source.
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
console.log('It's a local server!');
}
In my opinion, these are best way to check dev environment currently.
Therefore you should use location.hostname in your Firebase Hosting, and server.address() in Cloud Functions.
And define your Functions end point with constant variable.
const DEBUG = location.hostname === 'localhost' || location.hostname === '127.0.0.1';
const FUNCTIONS_ENDPOINT_DEV = 'http://localhost:5001/';
const FUNCTIONS_ENDPOINT_PRD = 'https://us-central1-something.cloudfunctions.net/';
const FUNCTIONS_URL_CALC = 'calcEEGcompensation?year=2013&month=1&size=10';
var endpoint;
if (DEBUG) {
endpoint = FUNCTIONS_ENDPOINT_DEV + FUNCTIONS_URL_CALC;
} else {
endpoint = FUNCTIONS_ENDPOINT_PRD + FUNCTIONS_URL_CALC;
}
Original answer(For Cloud Functions for Firebase):
Have you tried node.js net module's server.address() function?
This method will tell you if your functions code is running on localhost or real deployed server.
For examples, you can use like this.
const server = app.listen(function() {
let host = server.address().address;
let port = server.address().port;
if (!host || host === '::') {
host = 'localhost:';
}
console.log('Server is running on %s%s', host, port);
});
Iam trying to create a Dynamic page using cloud functions, but when i serve using cli in local, Iam getting the index.html page from public folder.
I tried deploying the functions,hosting in firebase but the problem stands still, were iam getting the index.html page from public folder
So for iam trying the following codes - I think i must be wrong with "rewrites" part.
/functions/index.js
const functions = require('firebase-functions');
exports.bigben = functions.https.onRequest((req, res) => {
const hours = (new Date().getHours() % 12) + 1 // london is UTC + 1hr;
res.status(200).send(`<!doctype html>
<head>
<title>Time</title>
</head>
<body>
${'BONG '.repeat(hours)}
</body>
</html>`);
});
firebase.json
{
"hosting": {
"public": "public",
// Add the following rewrites section *within* "hosting"
"rewrites": [ {
"source": "/", "function": "bigben"
} ]
}
}
What iam trying to do is when we hit https://example.firebaseapp.com/ , we have to get bigben functions response data.
Please some one give your hands!
Firebase Hosting will always prefer exact matches for static content over rewrites for performance reasons. Delete index.html from the public directory and your rewrite should begin working.