How to retrieve original host header on functions.https.Request? - firebase

I am working on a firebase cloud function that checks whether a user is signed in or not before allowing access to the requested page. If the Authentication header is not present in the HTTP request, then the user is redirected to the login page. Here is the code implemented:
exports.dashboard = functions.https.onRequest((req, res) => {
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) {
res.redirect(req.headers.host + "/login.html")
}
else {
//check for Firebase ID Token and return the requested page
}
});
The problem with this code is that req.headers.host does not return the original HTTP Request host header value, instead it comes back with the cloud function server address in which the function is running.
I also implemented URL rewriting, so this cloud function is actually triggered by an HTTPS Request as follows:
"hosting": {
"rewrites": [ {
"source": "/dashboard.html",
"function": "dashboard"
} ]
}
EDIT
The fact that I chose to rewrite an HTTPS Request to a Cloud Function HTTPS Request could cause this issue? Not sure how Firebase Hosting handles URL rewriting internally, but it seems like a new request is triggered from cloud server so the original HTTPS Request from the browser is lost (at least the host header shows that). Using Firebase Emulator, the HTTPS Request is posted from localhost:5000, but when writing req.headers.host to the console (from onRequest(req,res) function) it outputs localhost:5001, which is Functions server emulator.

I found the answer in another post:
req.headers['x-forwarded-host']

Related

Confirmation of why cross-origin problems occur when using signInWithRedirect are resolved

I know that the signInWithRedirect() flow in Firebase does not behave correctly in some browsers because it uses cross-origin.
https://github.com/firebase/firebase-js-sdk/issues/6716
When using signInWithRedirect(), the following article is a best practice.
https://firebase.google.com/docs/auth/web/redirect-best-practices
I have an app created in Next.js with authentication using signInWithRedirect() and deployed it to Vercel.
If I do nothing, it will not work in Safari as described in the above issue.
So I took the following 3 actions and confirmed that it works correctly.
Reverse proxy settings(next.config.js)
/** #type {import('next').NextConfig} */
module.exports = {
reactStrictMode: true,
async rewrites() {
return [
{
source: '/__/auth/:path*',
destination: `https://${process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN}/__/auth/:path*`,
},
]
},
}
Changed authDomain when initializing FirebaseApp to the app's domain
In GCP, change approved redirect URI to https://<the-domain-that-serves-my-app>/__/auth/handler.
These are the third method in Best practices.
I do not fully understand why this setup solves the problem, and I would like to correct my understanding.
My understanding is as follows
authDomain = a.com (domain of the app)
An authentication request is generated from the browser and redirected to authDomain (a.com)
The request is forwarded to https://<project>.firebaseapp.com/__/auth/ because a reverse proxy is set up
Host the login helper code to the identity provider at <project>.firebaseapp.com.
Return to a.com
Access login helper storage from browser
Authenticated redirect URI is set to the app domain, so the information is stored in the browser's storage in the app domain. And since the authDomain is the same as the app's, the authentication iFrame is referenced to it.
Is this understanding correct?

Browser not saving cookie sent by Golang backend

I know this question has been asked a bunch of times, but I tried most of the answers and still can't get it to work.
I have a Golang API with net/http package and a JS frontend. I have a function
func SetCookie(w *http.ResponseWriter, email string) string {
val := uuid.NewString()
http.SetCookie(*w, &http.Cookie{
Name: "goCookie",
Value: val,
Path: "/",
})
return val
}
This function is called when the user logs in, and I expect it to be sent to all the other endpoints. This works as expected with Postman. However, when it comes to the browser, I can't seem to get it to remember the cookie or even send it to other endpoints.
An example of JS using an endpoint
async function getDataWithQuery(query, schema){
let raw = `{"query":"${query}", "schema":"${schema}"}`;
let requestOptions = {
method: 'POST',
body: raw,
redirect: 'follow',
};
try{
let dataJson = await fetch("http://localhost:8080/query/", requestOptions)
data = await dataJson.json();
}catch(error){
console.log(error);
}
return data;
}
I tried answers like setting SameSite attribute in Golang, or using credential: "include" in JS with no luck.
Thanks to the discussion in the comments, I found some hints about the problem.
Saving cookies (both API and frontend on the same host)
I used document.cookie to save the cookie. I set the options by hand since calling res.cookie on the response of the API fetch only returned the value. An example is document.cookie = `goCookie=${res.cookie}; path=/; domain=localhost;.
Sending cookies
This has been answered before in previous questions and answered again in the comments. The problem was that I used credential:'include' instead of the correct credentials:'include' (plural).
CORS and cookies
In case the API and the frontend are not on the same host you will have to modify both the API and the frontend.
frontend
The cookie has to have the domain of the API since it's the API that requires it, not the frontend. So, for security reasons, you can't set a cookie for a domain (API) from another domain (frontend). A solution would be redirect the user to an API endpoint that returns Set-Cookie header in the response header. This solution signals the browser to register that cookie with the domain attached to it (the API's domain, since the API sent it).
Also, you still need to include credentials:'include' in the frontend.
API
You will need to set a few headers. The ones I set are
w.Header().Set("Access-Control-Allow-Origin", frontendOrigin)
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, withCredentials")
w.Header().Set("Access-Control-Allow-Methods", method) // use the endpoint's method: POST, GET, OPTIONS
You need to expose the endpoint where the frontend will redirect the user and set the cookie in the response. Instead of setting the domain of the API by hand, you can omit it, the browser will fill it with the domain automatically.
To handle the CORS and let JS send the cookie successfully, you will have to set the SameSite=None and Secure attributes in the cookie and serve the API over https (I used ngrok to make it simple).
Like so
func SetCookie(w *http.ResponseWriter, email string) string {
val := uuid.NewString()
http.SetCookie(*w, &http.Cookie{
Name: "goCookie",
Value: val,
SameSite: http.SameSiteNoneMode,
Secure: true,
Path: "/",
})
// rest of the code
}
I recommend you also read the difference between using localStorage and document.cookie, it was one of the problems I had.
Hope this helps.

How to check for authenticated users on Google Cloud Function

I am building a web site and decided to go pure HTML+JS with full Firebase so I don't have to implement a backend system to test new ideas. The use case for this question is that all users should be authenticated in order to get access to the pages (pretty standard security feature, right?).
To accomplish that, I am taking advantage of Google Cloud Functions to check whether a user is signed in or not before allowing access to the pages.
Here is the code implemented on firebase.json:
"hosting": {
"rewrites": [ {
"source": "/home.html",
"function": "home"
} ]
}
Inside the home function, I run the following code to check whether the Id Token is a valid one:
admin.auth().verifyIdToken(idToken).then((decodedToken) => {
const userId = decodedToken.uid;
})
The problem I am facing is that the value for idToken is invalid:
Firebase ID token has incorrect algorithm. Expected "none" but got
"RS256"
I tried to copy & past the value from result.credential.accessToken, but I still get the same error message.
firebase.auth().getRedirectResult().then(function(result) {
if (result.credential) {
var token = result.credential.accessToken;
}
});
Any help will be very appreciated.
Thanks!
I understand that you direct the HTTPS requests to your home HTTPS Cloud Function.
You should pass the Firebase ID token as a Bearer token in the Authorization header of the HTTP request, as explained and demonstrated in the following official Cloud Function sample.

Would URL rewrite make Firebase cloud function response come from my custom domain?

Firebase documentation mention that you can serve cloud functions using a custom domain using url rewrites.
You can use rewrites to serve a function from a Firebase Hosting URL.
The following example is an excerpt from serving dynamic content using
Cloud Functions.'
"hosting": {
// ...
// Directs all requests from the page /bigben to execute the bigben function
"rewrites": [ {
"source": "/bigben",
"function": "bigben"
} ]
}
Cloud function response can return values to set in a cookie the following way:
res.cookie("session", sessionCookie, {
expires: new Date(new Date().getTime() + expiresIn), // Add 2 weeks (in milliseconds) to the current epoch
httpOnly: true,
secure: true,
sameSite: "none",
domain: req.get("host")
});
// Return an HTTP 204 NO CONTENT response
return res.sendStatus(204);
However in most of browsers, 3rd party cookies are not allowed, only 1st party.
I understand I can call the cloud function using my custom domain thanks to the url rewrite, however what about the response, will it be consider 1st party or 3rd party?
From the browser's perspective the cookie will be coming from Firebase Hosting directly, and therefore the cookie will be considered a First Party Cookie. Notice that the rewrite in order to serve the Cloud Function from the Firebase Hosting URL all happens serverside, and therefore the behavior explained.

CORS error when use cors middleware in an api built with node.js and Express

I am new to node and express. I have encountered a cors error when I am building a very simple API. I have tried several hours to solve it in different method but none of these work.
Here's my approach
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({ origin: true }));
app.get('/api', (req, res) => {
res.send('Hello');
});
exports.api = functions.https.onRequest(app);
and got 4 errors all about :
http://localhost:3000 is not allowed by Access-Control-Allow-Origin.
I have also tried several other some methods like this:
var allowCrossDomain = function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Cache-Control");
next();
};
app.use(allowCrossDomain);
Which gives me the same error.
I am using Firebase Cloud Function to deploy this api, because the code is so simple so I really can not figure out which part is not doing right.
CORS is always a sticky situation, but in this case, I think I might be able to help. When you run firebase deploy you should see your endpoint get deployed. If it's the first time you are deploying that function, it should print out that new function's full URL in the console, it usually looks something like this:
https://us-central1-your-project-name.cloudfunctions.net/apiEndpointName
If you've already deployed it, you can see the function's full URL in the Firebase console -> Functions -> Dashboard
That URL is the normal public API endpoint or "HTTP-trigger" for that function. If you would use Postman to make a GET request to that URL, you should expect to receive your Hello response. (Or if you visited that URL in your browser, your browser would make a GET request to that URL, you should get your Hello response there too)
The problem comes when you want to access it from your deployed/hosted website. You need to tell the hosting portion of Firebase to route any traffic for /api to your function - your Firebase hosting should not try to resolve the /api route as a normal HTML page deployed along-side the primary index.html file... instead it should direct any traffic for /api to the cloud function api
So, you need to tell Firebase to direct any traffic for /api to the cloud function, not hosting. You give Firebase commands/configuration in the firebase.json file... in this case, under a section named "rewrites" like this:
{
"hosting": {
"public": "public",
// Add the following rewrites section *within* "hosting"
"rewrites": [ {
"source": "/bigben", "function": "bigben"
} ]
}
}
Check out this documentation link where it explains all that^^
Once you've done that, redeploy everything, and now you should be able to visit /api in your browser and trigger the function. NOTE Unless you are using firebase serve you should visit the route on the deployed website, not localhost. Check out this link for more details on firebase serve.

Resources