Failed to fetch a valid Google OAuth2 access token with firebase-admin - node.js

I currently cannot read or write data using firebase-admin in nodejs.
Without logging turned on it just hangs forever and never returns an error and never completes.
With logging turned on however it gives the following (it continues to retry on a decaying schedule - but with the same result):
p:0: Browser went online.
0: set {"path":"/server/saving-data/fireblog/users","value":{"alanisawesome":{"date_of_birth":"June 23, 1912","full_name":"Alan Turing"},"gracehop":{"date_of_birth":"December 9, 1906","full_name":"Grace Hopper"}},"zg":null}
p:0: Buffering put: /server/saving-data/fireblog/users
p:0: Making a connection attempt
p:0: Failed to get token: Error: Credential implementation provided to initializeApp() via the "credential" property failed to fetch a valid Google OAuth2 access token with the following error: "unable to verify the first certificate".
p:0: data client disconnected
p:0: Trying to reconnect in 0ms
0: onDisconnectEvents
When I catch the (internal to firebase-admin) https request I get the following:
{ Error: unable to verify the first certificate
at TLSSocket.<anonymous> (_tls_wrap.js:1094:38)
at emitNone (events.js:86:13)
at TLSSocket.emit (events.js:188:7)
at TLSSocket._finishInit (_tls_wrap.js:616:8)
at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:446:38) code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' }
I have tried generating a new private key from the console but it continues to return the same error. I have also added the require('ssl-root-cas').inject() as suggested elsewhere but it doesn't change the behavior. Also passing the actual json object to the admin.credential.cert function does not change the behavior.
I am currently on node v7.7.4.
Here is the code I have that produces the error:
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.cert('./serviceAccountKey.json'),
databaseURL: 'https://mtg-decks-bc92d.firebaseio.com'
});
// turn on logging
admin.database.enableLogging(true);
const db = admin.database();
const ref = db.ref('server/saving-data/fireblog');
const usersRef = ref.child('users');
usersRef.set({
alanisawesome: {
date_of_birth: 'June 23, 1912',
full_name: 'Alan Turing'
},
gracehop: {
date_of_birth: 'December 9, 1906',
full_name: 'Grace Hopper'
}
}).then(() => {
console.log('set complete');
});

Several other posts recommend adding the root certificates by calling require('ssl-root-cas').inject();. Can you try that?
Example: Error: unable to verify the first certificate in nodejs

I got this error because my local machines time zone was set manually so the token expiration etc was off.

Related

Cognito Identity service provider returning different cognito error for Refresh Token API from Jan 21st, 2022

I have a single userPool under which I have two client apps.
Using: amazon-cognito-identity-js, aws-sdk
Previously before (Jan 21, 2022 IST), when we signed in using one client app and then used the refresh token api to refresh the access token by passing another client app Id we were getting the error NotAuthorizedException: Invalid Refresh Token.
But since Jan 21, 2022 IST we are receiving a different error for the same scenario - NotAuthorizedException: Refresh Token has different Client.
I also tried with an updated amazon-cognito-identity-js library but the issue persists.
Node.js version: v12.18.4
SDK version number: amazon-cognito-identity-js#1.31.0, aws-sdk#2.177.0
To Reproduce (observed behavior)
Let us consider a userPoolId - UserPoolA
and two client app within the userpool - clientApp1 and clientApp2.
Login using clientApp1.
Use the refresh token received from step (1) to refresh the access token but this time pass the clientId as clientApp2
Initial Error Message: NotAuthorizedException: Invalid Refresh Token (Expected)
New Errror Message: NotAuthorizedException: Refresh Token has different Client (Unexpected, happening since January 21, 2022 IST)
Expected behavior
When we signed in using one client app and then used the refresh token api to refresh the access token by passing another client app Id we should be getting the error NotAuthorizedException: Invalid Refresh Token.
Additional context
Code Snippet:
// Let us consider a userPoolId - UserPoolA
// and two client app - clientApp1 and clientApp2.
const aws = require(aws-sdk);
const cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider();
// For logging in:
const params = {
AuthFlow: "ADMIN_NO_SRP_AUTH"
ClientId: **clientApp1**
UserPoolId: **UserPoolA**
AuthParameters: {
USERNAME: username,
PASSWORD: password
}
}
cognitoidentityserviceprovider.adminInitiateAuth(params, function (err, data) { }
// For refreshing the token:
const params = {
AuthFlow: 'REFRESH_TOKEN_AUTH',
ClientId: **clientApp2** ,
UserPoolId: **UserPoolA**
AuthParameters: {
REFRESH_TOKEN: **refreshToken received from step 1 Login.**
}
}
cognitoidentityserviceprovider.adminInitiateAuth(params, function (err, data) { }
P.S: We hadn't updated any version of the aws package before we started observing this change in message

Google Sign-In idToken with createSessionCookie causing error - there is no user record corresponding to the provided identifier

Stack:
Google Sign-in (Vanilla JS - client side),
Firebase Functions (ExpressJS)
Client-Side:
My Firebase function express app uses vanilla javascript on the client side. To authenticate I am making use of Firebase's Google SignIn feature client-side javascript web apps, found here.
// Firebase setup
var firebaseConfig = {
apiKey: "AIza...",
authDomain: "....firebaseapp.com",
databaseURL: "https://...-default-rtdb.firebaseio.com",
...
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
function postIdTokenToSessionLogin(idToken, csrfToken) {
return axios({
url: "/user/sessionLogin", < ----- endpoint code portion found below
method: "POST",
data: {
idToken: idToken,
csrfToken: csrfToken,
},
});
}
// ...
// On sign-in click
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth()
.signInWithPopup(provider)
.then(async value => {
const idToken = value.credential.idToken;
const csrfToken = getCookie('_csrf');
return postIdTokenToSessionLogin(idToken, csrfToken);
}).then(value => {
window.location.assign("/user/dashboard")
}).catch((error) => {
alert(error.message);
});
Note I am using value.credential.idToken (most sources imply to use this, but haven't found an example saying use this specifically)
Directly after calling signInWithPopup, a new account is created in my Firebase Console Authentication matching the gmail account that was just signed in.
Server-side:
Once I authenticate, I create an axios request passing in the {user}.credential.idToken and following the server-side setup here (ignoring the CSRF - this just doesn't want to work).
In creating the session, I use the following code in my firebase functions express app, the endpoint which is router.post('/sessionLogin', (req, res) => (part of /user route prefix):
// Set session expiration to 5 days.
const expiresIn = 60 * 60 * 24 * 5 * 1000;
const idToken = req.body.idToken.toString(); // eyJhbGciOiJSUzI1NiIsImt...[936]
admin
.auth()
.createSessionCookie(idToken, {expiresIn}) < ----------- Problem line
.then((sessionCookie) => {
// Set cookie policy for session cookie.
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
}).catch((error) => {
console.error(error);
res.status(401).send('UNAUTHORIZED REQUEST!');
});
On the createSessionCookie call, I get the following error & stack trace:
Error: There is no user record corresponding to the provided identifier.
at FirebaseAuthError.FirebaseError [as constructor] (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:44:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:90:28)
at new FirebaseAuthError (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:149:16)
at Function.FirebaseAuthError.fromServerError (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:188:16)
at C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\auth\auth-api-request.js:1570:49
at processTicksAndRejections (internal/process/task_queues.js:93:5)
This is part of the sign-in flow with a existing Gmail account.
What is causing this?
After many hours of searching, Googling - I have seen the light.
For some additional context, this error featured heavily in my struggle "Firebase ID token has invalid signature." - I will get to that in a second.
Further, another issue I also faced was using a local auth emulator for web client-side (javascript), see this for setup.
TL;DR to solve the immediate problem
Client-side remained largely the same, however the documentation provided by Firebase was inaccurate/misleading - thanks to this post, I found the solution. Thus, it follows...
Which is the ID Token? (Client-side):
The examples from here (to allow signInWithPopup), the response (if successful) results in
...
.signInWithPopup(provider)
.then((result) => {
/** #type {firebase.auth.OAuthCredential} */
var credential = result.credential;
// This gives you a Google Access Token. You can use it to access the Google API.
var token = credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
})
Looking for an idToken, I found one using result.credential.idToken but no where on the internet on if this was infact the correct token to use.
I ran into this error using the provided idToken above:
Firebase ID token has incorrect "aud" (audience) claim. Expected
"[insert your **projectId**]" but got
"59895519979-2l78aklb7cdqlth0eob751mdm67kt301.apps.googleusercontent.com".
Make sure the ID token comes from the same Firebase project as the
service account used to authenticate this SDK.
Trying other tokens like result.credential.accessToken responded with various verification errors - what to do?
Mention earlier, this solution on Github suggested to use firebase.auth().currentUser.getIdToken() AFTER you have signed in. An example (building on my previous code) is to do the following:
...
.signInWithPopup(provider)
.then((result) => {
// current user is now valid and not null
firebase.auth().currentUser.getIdToken().then(idToken => {
// send this ID token to your server
const csrfToken = getCookie('_csrf');
return postIdTokenToSessionLogin(idToken, csrfToken);
})
})
At this point, you can verify your token and createSessionCookies to your heart's desire.
BUT, a secondary issue I unknowingly created for myself using the Authentication Emulator.
To setup for client-side use:
var auth = firebase.auth();
auth.useEmulator("http://localhost:9099");
To setup for hosting your firebase functions app (assuming you are using this with e.g. nodejs + express, see this for setup, ask in comments, can provide more details if needed)
Using Authentication Emulator caused the following errors AFTER using the above mentioned "fix". Thus, DO NOT RUN the local authentication emulator (with Google sign-in of a valid Google account) as you will consistently get.
Firebase ID token has invalid signature. See
https://firebase.google.com/docs/auth/admin/verify-id-tokens for
details on how to retrieve an ID token
You can use all your local emulators, but (so far in my experience) you will need to use an online authenticator.

NodeJS: firebase-tools firestore delete failing

I am attempting to delete a sub collection when the parent document is deleted.
I have created the following cloud function to do so:
import { firestore, logger } from 'firebase-functions';
import { recursiveDelete } from '../utils/db';
export const deleteListItems = firestore.document('owners/{owner}/lists/{list}').onDelete(snap => {
const collPath = snap.ref.collection('items').path;
logger.info('Why do you hate me?');
logger.debug({ collPath });
return recursiveDelete(collPath);
});
---------
db.ts
---------------------------------
import * as functions from 'firebase-functions';
const firebase_tools = require('firebase-tools');
export const recursiveDelete = (path: string): Promise<any> => {
console.debug({ project: process.env.GCLOUD_PROJECT });
console.debug({ token: functions.config().fb.token });
return firebase_tools.firestore.delete(path, {
project: process.env.GCLOUD_PROJECT,
recursive: true,
yes: true,
token: functions.config().fb.token,
});
};
From my logging, I can see that the path I'm trying to delete is correct, owners/G-470728052545159171/lists/anime/items. I can also see that the project and token values are present.
I am, however, seeing the following error in the cloud functions logs:
deleteListItems
FirebaseError: Deletion failed. Errors: Failed to fetch documents to delete >= 3 times..
at Timeout.<anonymous> (/workspace/node_modules/firebase-tools/lib/firestore/delete.js:272:32)
at listOnTimeout (internal/timers.js:554:17)
at processTimers (internal/timers.js:497:7)
I ideally want the delete to be performed at the list doc level, but I thought that, that doc being deleted already might have been causing the error, which clearly it isn't.
I tried running the equivalent command in my console:
firebase firestore:delete -r -y owners/G-470728052545159171/lists/anime/items
This works as expected, even if the list doc (anime), is already deleted, so I'm not understanding why the one in the function isn't working.
EDIT: Set DEBUG env var
Debug output:
2:05:30.175 PM
deleteListItems
[2021-04-17T18:05:30.171Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
2:05:30.176 PM
deleteListItems
[2021-04-17T18:05:30.175Z] > authorizing via --token option
2:05:30.176 PM
deleteListItems
[2021-04-17T18:05:30.176Z] [iam] checking project watchlist-bot for permissions ["datastore.entities.delete","datastore.entities.list","firebase.projects.get"]
2:05:30.246 PM
deleteListItems
[2021-04-17T18:05:30.244Z] > refreshing access token with scopes: ["email","https://www.googleapis.com/auth/cloud-platform","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","openid"]
2:05:30.246 PM
deleteListItems
[2021-04-17T18:05:30.245Z] >>> HTTP REQUEST POST https://www.googleapis.com/oauth2/v3/token
2:05:30.246 PM
deleteListItems
<request body omitted>
2:05:30.478 PM
deleteListItems
[2021-04-17T18:05:30.477Z] <<< HTTP RESPONSE 400 {"cache-control":"no-cache, no-store, max-age=0, must-revalidate","date":"Sat, 17 Apr 2021 18:05:30 GMT","pragma":"no-cache","expires":"Mon, 01 Jan 1990 00:00:00 GMT","content-type":"application/json; charset=utf-8","vary":"X-Origin, Referer, Origin,Accept-Encoding","server":"scaffolding on HTTPServer2","x-xss-protection":"0","x-frame-options":"SAMEORIGIN","x-content-type-options":"nosniff","alt-svc":"h3-29=\":443\"; ma=2592000,h3-T051=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"","accept-ranges":"none","transfer-encoding":"chunked"}
2:05:30.581 PM
deleteListItems
[2021-04-17T18:05:30.581Z] <<< HTTP RESPONSE 401 {"www-authenticate":"Bearer realm=\"https://accounts.google.com/\", error=\"invalid_token\"","vary":"X-Origin, Referer, Origin,Accept-Encoding","content-type":"application/json; charset=UTF-8","date":"Sat, 17 Apr 2021 18:05:30 GMT","server":"ESF","cache-control":"private","x-xss-protection":"0","x-frame-options":"SAMEORIGIN","x-content-type-options":"nosniff","server-timing":"gfet4t7; dur=4","alt-svc":"h3-29=\":443\"; ma=2592000,h3-T051=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"","accept-ranges":"none","transfer-encoding":"chunked"}
2:05:30.581 PM
deleteListItems
[2021-04-17T18:05:30.581Z] <<< HTTP RESPONSE BODY {"error":{"code":401,"message":"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.","status":"UNAUTHENTICATED"}}
2:05:30.583 PM
deleteListItems
[2021-04-17T18:05:30.583Z] [iam] error while checking permissions, command may fail: FirebaseError: HTTP Error: 401, Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.
It seems there is an issue with my token, which I suspected, but I'm not sure why. I generated the token using firebase login:ci as they specified.
Then I set it using firebase functions:config:set fb.token=***.
As you can see in your log messages, your current token generated by login:ci is incorrectly missing the "https://www.googleapis.com/auth/cloud-platform" scope. Try updating your firebase-tools instance that you used to create the token and try generating a fresh token.
Regarding why this same error doesn't appear on your local system: On your system, firebase-tools makes use of your full account credential (to deploy code, etc) rather than a credential with targeted scopes.

InvalidAction: The action or operation requested is invalid. Verify that the action is typed correctly

I am using AWS Cognito Service Provider to create and list User Pool Clients. I have a locally installed DynamoDB to store the additional data. But I am getting the above error in the callback. I looked a lot for the error context but couldn't fine one.
const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
cognitoidentityserviceprovider.listUserPoolClients(params, function(clientListError, clientListData) {
console.log(clientListError)
if(clientListError){
return res.json({
status: false,
message: 'Error Fetching Client Apps',
data: clientListError
})
}
return res.json({
status: true,
message: 'List fetch success',
data: clientListData
})
});
This is for fetching the user pool client apps. In the same way I am creating the user pool client but I am getting the same error "InvalidAction"
The error thrown was from Dynamodb because I was connected to my local DB which had no tables and data and I was also not passing the credentials generated by the token manager. I removed the local DB URL from the config and then passed the credentials from the token manager and I got the desired result.
I am facing the same issue, but unable to solve it. Can you guide me on:
I removed the local DB URL from the config and then passed the credentials from the token manager and I got the desired result.
I am configuring DB this way:
static DB_CONFIG = AppConfig.ENVIRONMENT === 'localhost' ? { endpoint: 'http://localhost:8000', region: 'us-east-1' } : { region: 'us-east-1' };
which, in my case is localhost and first object gets passed in.

Firebase transaction not updating

I'm trying to increment a counter value in my Firebase database using transactions. it works the first time, i.e. when the value is set to 1, but on updates after that, the value isn't updating.
Here's how my code:
firebaseRef.child("search_hashtags").child(hashtag).transaction(function(currentValue) {
return (currentValue || 0) + 1;
}, function(error, committed, ss) {
console.log(error);
console.log(committed);
console.log(ss);
});
The error I log says "[Error: set]"
I turned on Firebase logging and I'm getting this:
p:0: from server: {"r":3,"b":{"s":"ok","d":""}}
p:0: p response {"s":"ok","d":""}
p:0: handleServerMessage d {"p":"search_hashtags/test","d":1}
p:0: from server: {"r":4,"b":{"s":"ok","d":{}}}
p:0: listen response {"s":"ok","d":{}}
p:0: from server: {"r":5,"b":{"s":"datastale","d":"Transaction hash does not match"}}
p:0: p response {"s":"datastale","d":"Transaction hash does not match"}
0: transaction put response {"path":"/search_hashtags/test","status":"datastale"}
I'm doing similar transactions on other nodes in other parts of my code and they work fine, but for some reason this one fails...

Resources