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.
Related
I have a Wordpress REST-API Backend with a UI5 JavaScript Frontend which loads some Post-Data to list them.
I want to protect this by a simple Login-Form in the Frontend.
To fetch and create the posts i use Node-WPAPI with basic authencation, like this:
this._oWp = new WPAPI({
endpoint: 'http://<domain>/wordpress/wp-json',
username: '<user>',
password: '<pass>'
});
But this approach dont fit my needs, because creating this WPAPI instance is even possible with a wrog password. Thus i would have to send a request to protected route first to know if my password was correct.
To directly know if my login was correct my idea was to firstly get a Token via "JWT Authentication for WP-API" and this actually works and i get the token.
But doing this my WPAPI Endpoints resulting with error "jwt_auth_bad_auth_header, "Authorization header malformed."
Is there a way to use WPAPI together with JWT Auth? Or how could a Login be authenticated by WPAPI Instance and get directly notified when the login credential were wrong?
I managed this with the WPAPI-Only-Approach, so proving that the user entered the right credentials by simply sending a request to .users().me() which needs a correct authentification. Otherwise you get an "Not authenticated" Error.
let oWp = new WPAPI({
endpoint: <your_endpoint>,
username: this._oAuth.getProperty("/Username"),
password: this._oAuth.getProperty("/Password"),
});
oWp.users().me()
.then( user => {
this._oWp.setData(oWp);
this._oRouter.navTo('home');
}).catch( error => {
console.log(error);
});
Manifest version 3 for Chrome extensions have been killing me lately. Been able to navigate around it so far, but this one has really stumped me. I'm trying to use Firebase authentication for a Chrome extension, specifically with 3rd party auth providers such as Google and Facebook. I've setup the Firebase configuration for Login with Google and created a login section in the options page of the Chrome extension and setup the Firebase SDK.
Now, there are two login options when using an auth provider, signInWithRedirect and signInWithPopup. I've tried both of these and both have failed for different reasons. signInWithRedirect seems like a complete dead end as it redirects to the auth provider, and when it attempts to redirect back to the chrome-extension://.../options.html page, it just redirects to "about:blank#blocked" instead.
When attempting to use signInWithPopup, I instead get
Refused to load the script 'https://apis.google.com/js/api.js?onload=__iframefcb776751' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
In v2, you could simply add https://apis.google.com to the content_security_policy in the manifest. But in v3, the docs say
"In addition, MV3 disallows certain CSP modifications for extension_pages that were permitted in MV2. The script-src, object-src, and worker-src directives may only have the following values:"
self
none
Any localhost source, (http://localhost, http://127.0.0.1, or any port on those domains)
So is there seriously no way for a Google Chrome extension to authenticate with a Google auth provider through Google's Firebase? The only workaround I can think of is to create some hosted site that does the authentication, have the Chrome extension inject a content script, and have the hosted site pass the auth details back to the Chrome extension through an event or something. Seems like a huge hack though and possibly subject to security flaws. Anyone else have ideas??
Although it was mentioned in the comments that this works with the Google auth provider using chrome.identity sadly there was no code example so I had to figure out myself how to do it.
Here is how I did it following this tutorial:
(It also mentions a solution for non-Google auth providers that I didn't try)
Identity Permission
First you need permission to use the chrome identity API. You get it by adding this to your manifest.json:
{
...
"permissions": [
"identity"
],
...
}
Consistent Application ID
You need your application ID consistent during development to use the OAuth process. To accomplish that, you need to copy the key in an installed version of your manifest.json.
To get a suitable key value, first install your extension from a .crx file (you may need to upload your extension or package it manually). Then, in your user data directory (on macOS it is ~/Library/Application\ Support/Google/Chrome), look in the file Default/Extensions/EXTENSION_ID/EXTENSION_VERSION/manifest.json. You will see the key value filled in there.
{
...
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgFbIrnF3oWbqomZh8CHzkTE9MxD/4tVmCTJ3JYSzYhtVnX7tVAbXZRRPuYLavIFaS15tojlRNRhfOdvyTXew+RaSJjOIzdo30byBU3C4mJAtRtSjb+U9fAsJxStVpXvdQrYNNFCCx/85T6oJX3qDsYexFCs/9doGqzhCc5RvN+W4jbQlfz7n+TiT8TtPBKrQWGLYjbEdNpPnvnorJBMys/yob82cglpqbWI36sTSGwQxjgQbp3b4mnQ2R0gzOcY41cMOw8JqSl6aXdYfHBTLxCy+gz9RCQYNUhDewxE1DeoEgAh21956oKJ8Sn7FacyMyNcnWvNhlMzPtr/0RUK7nQIDAQAB",
...
}
Copy this line to your source manifest.json.
Register your Extension with Google Cloud APIs
You need to register your app in the Google APIs Console to get the client ID:
Search for the API you what to use and make sure it is activated in your project. In my case Cloud Firestore API.
Go to the API Access navigation menu item and click on the Create an OAuth 2.0 client ID... blue button.
Select Chrome Application and enter your application ID (same ID displayed in the extensions management page).
Put this client ID in your manifest.json. You only need the userinfo.email scope.
{
...
"oauth2": {
"client_id": "171239695530-3mbapmkhai2m0qjb2jgjp097c7jmmhc3.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/userinfo.email"
]
}
...
}
Get and Use the Google Auth Token
chrome.identity.getAuthToken({ 'interactive': true }, function(token) {
// console.log("token: " + token);
let credential = firebase.auth.GoogleAuthProvider.credential(null, token);
firebase.auth().signInWithCredential(credential)
.then((result) => {
// console.log("Login successful!");
DoWhatYouWantWithTheUserObject(result.user);
})
.catch((error) => {
console.error(error);
});
});
Have fun with your Firebase Service...
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']
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.
I am trying to invalidate/revoke client's auth token when they sign on a different device. Initial auth-token is supplied through our server and not firebase (but uses the same secret key, hence works with firebase too).
For each user we save an associated password which gets passed as part of auth-token, when user switches the device - we issue a new password from the server and compare password to invalidate token on server. Firebase connection however still persists.
I am trying to store passwords on firebase for each user. This can then be updated every time we change the password at the backend and use it to invalidate the firebase token as well. However, I am not able to extract password from the auth object. Any ideas?
This is my firebase security rule.
{
"rules": {
".read": root.child('passwords').child(auth.uid).val() == auth.password,
".write": root.child('passwords').child(auth.uid).val() == auth.password
}
}
Surprisingly, none of the custom field in auth object are present.
Whatever you put in the custom auth object would be available in security rules and would be part of the authData returned after authenticating on the client. When you're creating the JWT, you have to follow Firebase's specific rules, which are outlined here. The important part being that a d key contains the authentication data you want to make available. Here's basic example of what the payload might look like.
{
"v": "0",
"iat": 1448391639,
"d": {
"uid": "user1234",
"password": "mySecretPassword"
}
}
http://jwt.io/ is a great tool for quickly creating JWTs for testing. If you use the payload above in conjunction with your Firebase secret, you should see the same payload within your auth data and security rules.
f.authWithCustomToken('eyJhbGciOiJIUz...', function(err, data) {
if (err) {
// Handle error
} else {
console.log(data.auth); // {uid: "userId1234", password: "mySecretPassword"}
}
});