How to verify Google signin (via Firebase) idToken in nodejs backend? - node.js

Trying to verify idToken of a user signed in via firebase authentication (Google signin) in nodejs server. Server throws Firebase ID token has invalid signature.
Tried verifying with firebase-admin as well as jsonwebtoken with public key from the url: https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com. Both methods work perfect for users signed in with a password, but throws 'Invalid Signature' in case of a user signed in via google.
Is there anything I am doing wrong? Do I need to verify with google-auth-library instead?
Code:
import * as admin from "firebase-admin";
admin.initializeApp({
credential: admin.credential.cert(require("../../serviceAccount")), // file received from firebase project settings page
databaseURL: "as mentioned in the firebase project settings page",
});
// Some code here
var token = "token received from client side";
var decoded = await admin.auth().verifyIdToken(token);
PS:
All client side features (after signing in) are working fine.
Everything else on the backend is working fine.
Decoding the token in both cases gives expected JSON.
For test run, token is being forceRefreshed everytime before calling the API.

OP here,
I am dumb.
I was using the print() function of flutter to log the token and call the API myself. Didn't know Flutter's print function has an output character limit. Login using password gives smaller tokens thus the whole token was logged. But Google sign in gives a longer token, longer than the output character limit of print.
Solution : Use log function from 'dart:developer' package.
import 'dart:developer';
//
log(await _auth.idToken);

const { OAuth2Client } = require("google-auth-library");
const client = new OAuth2Client(googleClient[process.env.ENV])
let token = 123456789011-crhch2kuum79bk0qr3usa39f7b9chikc.apps.googleusercontent.com
async function googleLoginVerify(token) {
try {
const ticket = await client.verifyIdToken({
idToken: token,
audience: googleClient[process.env.ENV],
});
const payLoad = ticket.getPayload();
return {
success: true,
data: payLoad,
};
} catch (err) {
console.log(err.message);
return {
success: false,
message: err.message,
};
}
}

Related

Appwrite authentication - "createJWT is not a function" error

I am trying to build a Node.js server-side signup function for user authentication. The data for the user is being sent via "req.body" and the authentication database is provided by Appwrite.
The signup function should:
Create a user with the credentials provided in the request body.
Return the user details, such as the username and email.
Generate and return a token (cookie/JWT)
I am encountering issues with the Appwrite documentation and would appreciate guidance on building this function.
When trying to POST a new user using the Users API, an error of
createJWT is not a function
is produced, and when using the Account API, an error of
User (role: guests) missing scope (account)
is produced.
Here's the code I have:
const sdk = require('node-appwrite')
const client = sdk.Client()
client
.setEndpoint(endpoint)
.setProject(projectId)
.setKey('...')
const users = sdk.Users(client)
async function signup(req, res) {
try {
const { email, username } = req.body
let { password } = req.body
password = await bcrypt.hash(password, SALT_ROUNDS)
const result = await users.createBcryptUser("unique()", email, password, username)
// Create a token
// Combine data
res.send(userWithToken)
} catch (err) {
error('Failed to signup', err)
throw new Error(err)
}
}
The Users API is intended to be used in an admin perspective rather than as a user. You can use the Account API to execute things on behalf of a user, but the JWT token is typically generated client side and passed to the server, where you can call client.setJWT().

How to get an idToken that is valid for development from firebase without having to spin up my frontend?

I am working on some firebase functions. This one will check if an user is logged in in firebase first. However this is a bit of a hassle in development. Now I need to login on the frontend first to get the id_token, pass it to my function url, then see the result.
The process I am following is described in the official docs: https://firebase.google.com/docs/auth/admin/verify-id-tokens
node.js
const admin = require('firebase-admin');
admin.initializeApp();
module.exports = function( request, response ) {
if( !request.query.id_token )
response.status(400).json({message: 'id token has not been provided'});
admin.auth()
.verifyIdToken( request.query.id_token )
.then( token => {
// TODO: redirect to payment portal
return response.status(200).json({message: 'Success'});
})
.catch( error => {
return response.status(401).json({message: 'You are currently not logged in as an authorised user'});
})
}
Is there a way to get an id_token that is valid from firebase without having to spin up my frontend? Good and simple alternatives solutions are welcome too.
NOTE: I am using the firebase emulators during development.
Since you're using the Firebase emulators you may create a fake user and retrieve an id token programmatically. The code below creates and logs in a user and returns an id_token that will be accepted by your function.
var firebase = require("firebase/app");
require("firebase/auth");
// Initialize Firebase and connect to the Authentication emulator
var firebaseConfig = {
// Insert Firebase config here
};
firebase.initializeApp(firebaseConfig);
firebase.auth().useEmulator('http://localhost:9099/');
// Create a fake user and get the token
firebase.auth().createUserWithEmailAndPassword("example#example.com", "password")
.then((userCredential) => {
console.log("User created")
});
firebase.auth().signInWithEmailAndPassword("example#example.com", "password")
.then((userCredential) => {
console.log("User logged in")
userCredential.user.getIdToken().then((idToken) => {
console.log(idToken)
});
});

How to resolve Nodejs google-auth-library invalid token signature error?

I'm using flutter for my mobile app. I try to add sign in with google. Everything is okay for Flutter side. I'm gettin idToken from mobile app and send to my backend, nodejs.
Now, I want to use this idToken to authenticate user's requests on nodejs backend side with google-auth-library package.
let token = "token"
const CLIENT_ID = "client_id"
const { OAuth2Client } = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);
async function verify() {
try {
const ticket = await client.verifyIdToken({
idToken: token,
audience: CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
console.log(payload)
} catch (error) {
console.log(error)
}
}
verify()
But this code always returns this error => Error: Invalid token signature:
at OAuth2Client.verifySignedJwtWithCertsAsync (\node_modules\google-auth-library\build\src\auth\oauth2client.js:566:19)
What should I do for to verify this idToken on nodejs backend side?
Thanks.
If the idToken that you are passing to the function is from the log of your flutter app, it is likely that you are not getting the entire idToken printed in the log due to the limitations of print().
I used the below code snippet to print out the idToken and used that in the API which gave me a success response.
print('ID TOKEN');
String token = googleAuth.idToken;
while (token.length > 0) {
int initLength = (token.length >= 500 ? 500 : token.length);
print(token.substring(0, initLength));
int endLength = token.length;
token = token.substring(initLength, endLength);
}

How to create Firebase token on server for use with unit tests?

I need to authenticate a Firebase user using node so I can test some server side methods. For each protected request, I verify the Firebase token using:
firebase.auth().verifyIdToken(firebaseAccessToken).then(function(decodedToken) {
// forward request
})
So in my test I created a token with a uid from my Firebase database
firebase.auth().createCustomToken(uid).then(function(token) {
//add to header for requests
})
Later I read that custom tokens are not verified by the verifyIdToken method, only client generated ones.
I've looked at this answer - server side verification of tokens in firebase
So I added databaseAuthVariableOverride to the init json
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: [dbURL],
databaseAuthVariableOverride: {
uid: [uid]
}
});
Still getting the output in my tests
Error: expected 200 "OK", got 401 "Unauthorized"
And the firebase error -
Error: Decoding Firebase ID token failed. Make sure you passed the entire string JWT which represents an ID token. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
So how do I emulate a user with my current setup?
Here's a Python script for generating Firebase ID tokens (not custom tokens).
python firebase_token_generator.py <UID>
There are probably easier ways to do this but you could call the Python script from Node.
You can generate a Firebase Id token from your custom token, then use that for verification. Eg:
const rp = require("request-promise");
// 'customToken' comes from FirebaseAdmin.auth().createCustomToken(uid)
function getIdTokenFromCustomToken(customToken) {
const url = `https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=${API_KEY}`;
const data = {
token: customToken,
returnSecureToken: true
};
var options = {
method: "POST",
uri: url,
body: data,
json: true // Automatically stringifies the body to JSON
};
return rp(options)
// idToken is the firebase id token that can be used with verifyIdToken
.then(parsedBody => parsedBody.idToken)
.catch(function(err) {
// POST failed...
});
}

Firebase 3.0 Tokens : [Error: Invalid claim 'kid' in auth header.]

I'm trying to create JWT tokens in node.js for use with the REST api in firebase, but when I try to use them, I get the error "Error: Invalid claim 'kid' in auth header."
This is my code
http.createServer(function (req, res) {
var payload = {
uid: "bruh"
};
var token = jwt.sign(payload, sact["private_key"], {
algorithm: 'RS256',
issuer: sact["client_email"],
subject: sact["client_email"],
audience: 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit',
expiresIn: '3600s',
jwtid: sact["private_key_id"],
header: {
"kid": sact["private_key_id"]
}
});
res.writeHead(200);
res.end("It worked. (" + token + ")");
}).listen(port);
These are my requires
var http = require('http');
var jwt = require('jsonwebtoken');
Please use returnSecureToken: true, with correct Spellings
I hope it will solve the problem of Invalid claim 'kid' in the auth header.
This is an issue because you're generating a Firebase ID token, not an access token for the Firebase REST API.
To generate a REST API token I would use the legacy Firebase Token Generator library which still works perfectly well (but only generates REST tokens, not general purpose access tokens).
Note that your Firebase Database secret is now located under the gear icon in the top left of the console.
So I had this error and I've fixed it. Now here is the solution:
You'll need to retrieve the ID-token using an additional function. Here is the function you can use:
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
I implemented it somewhat like this:
//google OAuth login handler
const googleLoginHandler = () => {
const provider = new firebase.auth.GoogleAuthProvider();
firebase.auth()
.signInWithPopup(provider)
.then((result) => {
/** #type {firebase.auth.OAuthCredential} */
setgoogleAuthStatus(true)
// The signed-in user info.
const userId = result.user.uid;
const displayName = result.user.displayName;
const email = result.user.email;
//This is the function for getting the ID-Token
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then((idToken) => {
// Send token to your backend via HTTPS
console.log(idToken)
}).catch((error) => {
// Handle error
console.log(error.message)
alert(error.message)
});
console.log(result)
}).catch((error) => {
console.log(error)
// Handle Errors here.
alert(error.message)
})
}
The id token you get by this method can be used to access the firebase real-time database and other firebase services.
check out these links for more details:
https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients
https://firebase.google.com/docs/database/rest/auth#firebase_id_tokens

Resources