Cloud Function to Authenticate a User - node.js

I am attempting to authenticate a user to access various scopes in the user Gsuite. I can run the code locally but I cannot seem to get it accepted as a cloud function.
I have tried deploying with firebase and with gcloud. I have checked my eslint settings.
This code is coming from https://github.com/googleapis/google-api-nodejs-client/blob/master/README.md#oauth2-client
'use strict';
const fs = require('fs');
const path = require('path');
const http = require('http');
const url = require('url');
const opn = require('open');
const destroyer = require('server-destroy');
const {google} = require('googleapis');
/**
* To use OAuth2 authentication, we need access to a a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI. To get these credentials for your application, visit https://console.cloud.google.com/apis/credentials.
*/
const keyPath = path.join(__dirname, 'credentials.json');
let keys = {redirect_uris: ['']};
if (fs.existsSync(keyPath)) {
keys = require(keyPath).web;
}
/**
* Create a new OAuth2 client with the configured keys.
*/
const oauth2Client = new google.auth.OAuth2(
keys.client_id,
keys.client_secret,
`http://localhost:3000/oauth2callback`
);
/**
* This is one of the many ways you can configure googleapis to use authentication credentials. In this method, we're setting a global reference for all APIs. Any other API you use here, like google.drive('v3'), will now use this auth client. You can also override the auth client at the service and method call levels.
*/
google.options({auth: oauth2Client});
const scopes = ['https://www.googleapis.com/auth/documents'];
/**
* Open an http server to accept the oauth callback. In this simple example, the only request to our webserver is to /callback?code=<code>
*/
async function authenticate(){
// grab the url that will be used for authorization
const authorizeUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes
});
const server = http.createServer(async (req, res) => {
try {
if (req.url.indexOf('/oauth2callback') > -1) {
const qs = new url.URL(req.url, 'http://localhost:3000').searchParams;
res.end('Authentication successful! Please return to the console.');
server.destroy();
const {tokens} = await oauth2Client.getToken(qs.get('code'));
oauth2Client.credentials = tokens; // eslint-disable-line require-atomic-updates
resolve(oauth2Client);
}
} catch (e) {
reject(e);
}
})
.listen(3000, () => {
// open the browser to the authorize url to start the workflow
opn(authorizeUrl, {wait: false}).then(cp => cp.unref())
.catch(
error => {
console.log(error);
});
});
destroyer(server)
.then(client => runSample(client)).catch(
error => {
console.log(error);
});
};
module.exports.authenticate=authenticate;
async function runSample(client) {
// retrieve user profile
console.log(client);
const docs = google.docs({
version: 'v1',
auth: client
});
const createResponse = await docs.documents.create({
requestBody: {
title: 'Your new document!',
},
});
}
I expect it to load as a cloud function to firebase or gcloud.
However:
Firebase returns "Deploy complete" but it never shows in the functions.
gcloud returns "SyntaxError: Unexpected token function" with the word function indicated in "async function authenticate(){"
I'm new to node.js and may be missing something really obvious to others.

You will never get User Credentials (Client ID/Client Secret) to work
in Cloud Functions (meaning authenticate and create credentials).
OAuth requires a web browser and a human. Neither one exists in Cloud
Functions. Use a Service Account instead. – John Hanley

Related

Could not load the default credentials? (Node.js Google Compute Engine)

I am trying to create a new vm using Nodejs client libraries of GCP, I followed the below link,
https://googleapis.dev/nodejs/compute/latest/VM.html#create
and below is my code
const Compute = require('#google-cloud/compute');
const {auth} = require('google-auth-library');
const compute = new Compute();
var cred = "<<<credential json content as string>>>";
auth.scopes = ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/compute'];
auth.jsonContent = JSON.parse(cred);
const config = {
machineType: 'n1-standard-1',
disks: [ {
boot: true,
initializeParams: { sourceImage: '<<<image url>>>' }
} ],
networkInterfaces: [ { network: 'global/networks/default' } ],
tags: [ { items: [ 'debian-server', 'http-server' ] } ],
auth: auth,
};
async function main() {
// [START gce_create_vm]
async function createVM() {
const zone = compute.zone('us-central1-c');
const vm = zone.vm('vm-name');
await vm.create(config).then(function(data) {
const vm = data[0];
const operation = data[1];
const apiResponse = data[2];
});
console.log(vm);
console.log('Virtual machine created!');
}
createVM().catch(function (err) {
console.log(err);
});
// [END gce_create_vm]
}
main();
when i run this, the error I am getting is
Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.
at GoogleAuth.getApplicationDefaultAsync (D:\Click to deploy\src\c2dNodeGCP\node_modules\google-auth-library\build\src\auth\googleauth.js:155:19)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async GoogleAuth.getClient (D:\Click to deploy\src\c2dNodeGCP\node_modules\google-auth-library\build\src\auth\googleauth.js:487:17)
at async GoogleAuth.authorizeRequest (D:\Click to deploy\src\c2dNodeGCP\node_modules\google-auth-library\build\src\auth\googleauth.js:528:24)
My scenario is to take the service account credential from string variable rather than from env var or some other thing.
I can see that it is trying to take the default credential which is not there in my case.
I was able to achieve this in java, but here i am not able to do it. Any help will be appreciated.
In order to execute your local application using your own user credentials for API access temporarily you can run:
gcloud auth application-default login
You have to install sdk into your computer, that will enable you to run the code.
Then log in to your associated gmail account and you will be ready.
You can check the following documentation, to get more information.
Another option is to set GOOGLE_APPLICATION_CREDENTIALS to provide authentication credentials to your application code. It should point to a file that defines the credentials.
To get this file please follow the steps:
Navigate to the APIs & Services→Credentials panel in Cloud Console.
Select Create credentials, then select API key from the dropdown menu.
The API key created dialog box displays your newly created key.
You might want to copy your key and keep it secure. Unless you are using a testing key that you intend to delete later.
Put the *.json file you just downloaded in a directory of your choosing.
This directory must be private (you can't let anyone get access to this), but accessible to your web server code.
You can write your own code to pass the service account key to the client library or set the environment variable GOOGLE_APPLICATION_CREDENTIALS to the path of the JSON file downloaded.
I have found the following code that explains how you can authenticate to Google Cloud Platform APIs using the Google Cloud Client Libraries.
/**
* Demonstrates how to authenticate to Google Cloud Platform APIs using the
* Google Cloud Client Libraries.
*/
'use strict';
const authCloudImplicit = async () => {
// [START auth_cloud_implicit]
// Imports the Google Cloud client library.
const {Storage} = require('#google-cloud/storage');
// Instantiates a client. If you don't specify credentials when constructing
// the client, the client library will look for credentials in the
// environment.
const storage = new Storage();
// Makes an authenticated API request.
async function listBuckets() {
try {
const results = await storage.getBuckets();
const [buckets] = results;
console.log('Buckets:');
buckets.forEach((bucket) => {
console.log(bucket.name);
});
} catch (err) {
console.error('ERROR:', err);
}
}
listBuckets();
// [END auth_cloud_implicit]
};
const authCloudExplicit = async ({projectId, keyFilename}) => {
// [START auth_cloud_explicit]
// Imports the Google Cloud client library.
const {Storage} = require('#google-cloud/storage');
// Instantiates a client. Explicitly use service account credentials by
// specifying the private key file. All clients in google-cloud-node have this
// helper, see https://github.com/GoogleCloudPlatform/google-cloud-node/blob/master/docs/authentication.md
// const projectId = 'project-id'
// const keyFilename = '/path/to/keyfile.json'
const storage = new Storage({projectId, keyFilename});
// Makes an authenticated API request.
async function listBuckets() {
try {
const [buckets] = await storage.getBuckets();
console.log('Buckets:');
buckets.forEach((bucket) => {
console.log(bucket.name);
});
} catch (err) {
console.error('ERROR:', err);
}
}
listBuckets();
// [END auth_cloud_explicit]
};
const cli = require(`yargs`)
.demand(1)
.command(
`auth-cloud-implicit`,
`Loads credentials implicitly.`,
{},
authCloudImplicit
)
.command(
`auth-cloud-explicit`,
`Loads credentials explicitly.`,
{
projectId: {
alias: 'p',
default: process.env.GOOGLE_CLOUD_PROJECT,
},
keyFilename: {
alias: 'k',
default: process.env.GOOGLE_APPLICATION_CREDENTIALS,
},
},
authCloudExplicit
)
.example(`node $0 implicit`, `Loads credentials implicitly.`)
.example(`node $0 explicit`, `Loads credentials explicitly.`)
.wrap(120)
.recommendCommands()
.epilogue(
`For more information, see https://cloud.google.com/docs/authentication`
)
.help()
.strict();
if (module === require.main) {
cli.parse(process.argv.slice(2));
}
You could obtain more information about this in this link, also you can take a look at this other guide for Getting started with authentication.
Edit 1
To load your credentials from a local file you can use something like:
const Compute = require('#google-cloud/compute');
const compute = new Compute({
projectId: 'your-project-id',
keyFilename: '/path/to/keyfile.json'
});
You can check this link for more examples and information.
This other link contains another example that could be useful.

NodeJS Googleapis Service Account authentication

I'm trying to perform authentication on GoogleAPIs using a Service Account. I have a service account set up, with its credentials located at credentials.json. I try to access a private sheet, to which I added the E-Mail address of the service account with editing rights.
Here the code I am using:
const {
google
} = require('googleapis');
const fs = require('fs');
let scopes = ['https://www.googleapis.com/auth/spreadsheets'];
let credentials = require("./credentials.json");
const authClient = new google.auth.JWT(
credentials.client_email,
null,
credentials.private_key,
scopes);
authClient.authorize(function(err, tokens) {
if (err) {
console.log(err);
return;
} else {
authClient.setCredentials(tokens);
}
});
const sheets = google.sheets({
version: 'v4',
authClient
});
let spreadsheetId = //...
let range = //...
const request = {
spreadsheetId: spreadsheetId,
range: range
};
sheets.spreadsheets.values.get(request, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
} else {
console.log('Result: ' + response);
}
});
I guess the API changed over time, since many guides showed different approaches, and in the end none worked for me.
The error is as follows:
The API returned an error: Error: The request is missing a valid API key.
To my understanding, a simple API key should only be necessary for unauthenticated access on public sheets, so I don't get why it is even requiring that. If I add such an API key I get the error
The API returned an error: Error: The caller does not have permission
Using
$ npm list googleapis
`-- googleapis#52.1.0
Any help would be greatly appreciated.
For who still facing googleapis problems within NodeJS Runtime in 2022.
Firstly, redirect into Google-IAM-Admin/ServiceAccount to pick the current working project.
Secondly, click to jump into Service Account that has the following format project#sub-name-id.iam.gserviceaccount.com.
Thirdly, between [Details, Permissions, Keys, Metrics, Logs]. Jump into Keys then Add Key -> Create new Key -> Key type::JSON and save JSON file to your computer.
Here within NodeJS Runtime, I use the following Semantic Version
googleapis#100.0.0
You can create JWT Client and inject into google default auth at google.options({auth: client}); or provide auth-client to specific Service as google.chat({version: 'v1', auth: client});
However, in the following example. I create a GoogleAuth instance and then make an AuthClient after. Which resulted the same behaviour to the JWT Method.
/** Import Node Native Dependencies !*/
import * as path from "path";
/** Import ES6 Default Dependencies !*/
import {google} from "googleapis";
const {client_email, private_key} = require('$/keys/credentials.json');
/**
** #description - Google [[Service Account]] Authenticator.
**/
const auth = new google.auth.GoogleAuth({
keyFile: path.resolve('keys/credentials.json'),
/** Scopes can be specified either as an array or as a single, space-delimited string; ~!*/
scopes: [
"https://www.googleapis.com/auth/chat.bot",
],
});
const client = new google.auth.JWT({
email: client_email,
key: private_key,
/** Scopes can be specified either as an array or as a single, space-delimited string; ~!*/
scopes: [
"https://www.googleapis.com/auth/chat.bot",
],
});
(async () => {
/** #description - Either [[Get Client]] from [Google Auth] or Use directly from [JWT Client] ~!*/
const client = await auth.getClient();
/** #description - Use this Authorized Client as Default Authenticated to fallback from [Non-Authenticated Services] ~!*/
google.options({auth: client});
const chat = google.chat({
version: 'v1',
/** #description - Provide [Authenticated Services] to [Google Chat Service] Instance ~!*/
auth: client,
});
const response = await chat.spaces.members.get({
// Required. Resource name of the attachment, in the form "spaces/x/messages/x/attachments/x".
name: 'spaces',
});
console.log('response', response.data);
return void 0;
})();

app.auth(...).signInWithCredential is not a function firebase error

I'm trying to add google login to my node js firebase app. I'm getting the following error:
app.auth(...).signInWithCredential is not a function
Here's the code on the server
var credential = firebase.auth.GoogleAuthProvider.credential(id_token);
return app.auth().signInWithCredential(credential);
On the client
// We need to register an Observer on Firebase Auth to make sure auth is initialized.
var unsubscribe = app.auth().onAuthStateChanged((firebaseUser) => {
unsubscribe();
// Check if we are already signed-in Firebase with the correct user.
// Build Firebase credential with the Google ID token.
var credential = firebase.auth.GoogleAuthProvider.credential(googleUser.getAuthResponse().id_token);
// Sign in with credential from the Google user.
firebase.auth().signInWithCredential(credential).catch( (error) => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
console.dir(errorMessage);
}).then( (success) => {
console.dir(success);
var id_token = googleUser.getAuthResponse().id_token;
axios.post('/auth/google-login', {
id_token: id_token
}).then(response => {
var result = response.data;
if (result.status === "ok") {
this.setState({complete: true});
} else if (result.error) {
this.setState({error: result.error});
}
}).catch(error => {
console.dir(error);
this.setState({error: "error"});
});
});
});
What I'm I doing wrong?
At the top of the nodejs file, I have
var app = require('./base');
where base :
var serviceAccount = require("./service-account-file.json");
const app = fadmin.initializeApp({
credential: fadmin.credential.cert(serviceAccount),
databaseURL: "https://test.firebaseio.com"
});
module.exports = app;
Hi there are hints that the problem starts with node version 6.21. that
app.auth().signInWithCredential(credential)
Don't work
It works in v6.2.0.
Try signInWithEmailAndPassword
Use latest version of node
The function signInWithCredential is part of the Firebase JavaScript SDK
The Firebase JavaScript SDK implements the client-side libraries used by applications using Firebase services.
The Firebase Admin SDK is intended to run on a server or other privileged environment. The Admin SDK does not include sign-in functions.

Post Request Firebase Cloud Functions Timing Out

I know that each HTTP function must end with end() or send(), so I'm thinking that might be related to my issue. I'm building a Shopify app that I want to host on Firebase. I've gotten it to authenticate and install, but when I try to capture the permanent access token via POST, Firebase times out. This same code works fine with ngrok. Entire route function below.
const dotenv = require('dotenv').config();
const functions = require('firebase-functions');
const express = require('express');
const app = express();
const crypto = require('crypto');
const cookie = require('cookie');
const nonce = require('nonce')();
const querystring = require('querystring');
const request = require('request-promise');
const apiKey = process.env.SHOPIFY_API_KEY;
const apiSecret = process.env.SHOPIFY_API_SECRET;
const scopes = 'read_products,read_customers';
const forwardingAddress = 'https://my-custom-app.firebaseapp.com/app';
app.get('/app/shopify/callback', (req, res) => {
const { shop, hmac, code, state } = req.query;
const stateCookie = cookie.parse(req.headers.cookie).__session;
if (state !== stateCookie) {
return res.status(403).send('Request origin cannot be verified');
}
if (shop && hmac && code) {
// DONE: Validate request is from Shopify
const map = Object.assign({}, req.query);
delete map['signature'];
delete map['hmac'];
const message = querystring.stringify(map);
const generatedHash = crypto
.createHmac('sha256', apiSecret)
.update(message)
.digest('hex');
if (generatedHash !== hmac) {
return res.status(400).send('HMAC validation failed');
}
// Collect permanent access token
const accessTokenRequestUrl = 'https://' + shop + '/admin/oauth/access_token';
const accessTokenPayload = {
client_id: apiKey,
client_secret: apiSecret,
code,
};
// Everything works up until here
request.post(accessTokenRequestUrl, { json: accessTokenPayload })
.then((accessTokenResponse) => {
const accessToken = accessTokenResponse.access_token;
// If below is uncommented, it will not show on browser, Firebase seems to timeout on the above request.post.
//res.status(200).send("Got an access token, let's do something with it");
// Use access token to make API call to 'shop' endpoint
const shopRequestUrl = 'https://' + shop + '/admin/shop.json';
const shopRequestHeaders = {
'X-Shopify-Access-Token': accessToken,
};
request.get(shopRequestUrl, { headers: shopRequestHeaders })
.then((shopResponse) => {
res.end(shopResponse);
})
.catch((error) => {
res.status(error.statusCode).send(error.error.error_description);
});
})
.catch((error) => {
res.status(error.statusCode).send(error.error.error_description);
});
} else {
res.status(400).send('Required parameters missing');
}
});
exports.shopifyValidate = functions.https.onRequest(app);
You're calling response.end() incorrectly:
request.get(shopRequestUrl, { headers: shopRequestHeaders })
.then((shopResponse) => {
res.end(shopResponse);
})
As you can see from the linked documentation, end() doesn't take a parameter. It just ends the response. You probably want to be calling send() instead if you have data to send.
If you're unsure how your function is executing, also use console.log() to log messages to figure out exactly what it's doing. It's rarely a good idea to just hope that a bunch of code is just working - you should verify that it's working the way you expect.
Solved. Turns out you need a paid plan (Blaze, pay as you go) to access external APIs. I upgraded and that solved the issue.
What is the request module that you are using for the request.post()
Please see : https://www.npmjs.com/package/request#promises--asyncawait
I hope you are using the https://github.com/request/request-promise module instead of request.

google-auth-library v.1.0 how to use just for identity?

I,ve been using google-auth-library#0.10.0 nodejs just for verifying user identity in my api services, now it changed to 1.0 and everything is broken.
I previously used example from here:
https://developers.google.com/identity/sign-in/web/backend-auth
now I cannot figure out how to verify identity using the new library.
Examples here: https://github.com/google/google-auth-library-nodejs
explains how to get access to google apis, I just need to verify identity.
this is my code:
const GoogleAuth = require('google-auth-library');
const auth = new GoogleAuth.GoogleAuth();
const google = require('googleapis');
const authData = {
'googleAuth': {
'clientID': 'xxxxxxxxxxx-aaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com',
'clientSecret': 'sssssssssssssssssssssssss',
'callbackURL': 'http://localhost:121212/auth/'
}
};
const CLIENT_ID = authData.googleAuth.clientID;
function verifyToken(token) {
let tokenPromise = new Promise(function(resolve, reject) {
client.verifyIdToken(
token,
CLIENT_ID,
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3],
function(e, login) {
if (e) {
return reject(e);
} else {
var payload = login.getPayload();
var userid = payload['sub'];
//console.log(payload, userid, e, login);
return resolve(login);
// If request specified a G Suite domain:
//var domain = payload['hd'];
}
});
});
return tokenPromise;
};
it was working fine, just to get googleUserId.
now I've this error:
Error: This method accepts an options object as the first parameter, which includes the idToken, audience, and maxExpiry.
I understand I'm not passing properly parameters and maybe its not the method to use..but how can I know which method and how it accepts parameters??? I cannot find any documentation about google-auth-library#1.0...some one is using it in production??
The verifyIdToken changed. Here's a working example.
import { OAuth2Client } from 'google-auth-library';
const client = new OAuth2Client(
'xxxxxxxxxxx-aaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com,
'sssssssssssssssssssssssss',
'http://localhost:121212/auth/',
);
client.verifyIdToken({
idToken: TOKEN,
audience: CLIENT_ID_1 // If you have multiple [CLIENT_ID_1, CLIENT_ID_2, ...]
}, (err, login) => {
console.log(login);
});
Just tested this recently.
Google needs to update their documentation.
Please note that you can specify a maxAge to the verify id token options as well.
Here's the interface copied from the source code
export interface VerifyIdTokenOptions {
idToken: string;
audience: string|string[];
maxExpiry?: number;
}
As you can see the idToken and audience are required, the maxExpiry is optional.
Source can be found here

Resources