Search code examples
node.jsjwtgoogle-cloud-functionsgoogle-admin-sdkservice-accounts

NodeJS Example - Firebase Cloud Functions - Instantiate an Admin SDK Directory service object


Goal

Use googleapis with Firebase Cloud Functions to get a list of all users in my G Suite domain.

Question

How do I Instantiate an Admin SDK Directory service object. I do not see a NodeJS example, and I'm not clear how to setup and make the request with googleapis.

Context

This code runs from Firebase Cloud Functions, and it seems to authenticate okay. Now, how do I setup the service object at //TODO in the following code:

// Firebase Admin SDK
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)

// Google APIs
const googleapis = require('googleapis')
const drive = googleapis.drive('v3')
const gsuiteAdmin = googleapis.admin('directory_v1')

// Service Account Key - JSON
let privatekey = require("./privatekey.json")

let jwtClient = new googleapis.auth.JWT(
    privatekey.client_email,
    null,
    privatekey.private_key,
    ['https://www.googleapis.com/auth/drive',
        'https://www.googleapis.com/auth/admin.directory.user'])

// Firebase Cloud Functions - REST
exports.authorize = functions.https.onRequest((request, response) => {
    //authenticate request
    jwtClient.authorize(function (err, tokens) {
        if (err) {
            console.log(err)
            return
        } else {
            console.log("Successfully connected!")
        }

        // TODO
        // USE SERVICE OBJECT HERE??
        // WHAT DOES IT LOOK LIKE?

        response.send("Successfully connected!")
    })
})

Solution

  • Order of Operations:

    1. Create Service Account Credentials in Google Cloud Console
    2. Add Domain-Wide Delegation to the Service Account
    3. Authorize the API in G Suite - Security - Advanced
    4. Go back to the Service Account and Download the .json key file

    I downloaded the .json key file too soon, e.g., before authorizing the APIs in G Suite. The order, Setting up the Service Account with DwD and then authorization the API in G Suite API and then downloading the .json key file is important.

    The Example

    // Firebase Admin SDK
    const functions = require('firebase-functions')
    const admin = require('firebase-admin')
    admin.initializeApp(functions.config().firebase)
    
    // Google APIs
    const googleapis = require('googleapis')
    const drive = googleapis.drive('v3')
    const directory = googleapis.admin('directory_v1')
    
    // Service Account Key - JSON
    let privatekey = require("./privatekey.json")
    let impersonator = '[email protected]'
    
    let jwtClient = new googleapis.auth.JWT(
        privatekey.client_email,
        null, // not using path option
        privatekey.private_key,
        ['https://www.googleapis.com/auth/drive',
            'https://www.googleapis.com/auth/admin.directory.user',
            'https://www.googleapis.com/auth/admin.directory.user.readonly'],
        impersonator
    )
    
    // Firebase Cloud Functions - REST
    exports.getUsers = functions.https.onRequest((request, response) => {
        //authenticate request
        jwtClient.authorize(function (err, tokens) {
            if (err) {
                console.log(err)
                return
            } else {
                console.log("Successfully connected!")
            }
            //Google Drive API
            directory.users.list ({
                auth: jwtClient,
                domain: 'example.com',
                maxResults: 10,
                orderBy: 'email',
                viewType: 'domain_public'
              }, function(err, res) {
                if (err) {
                  console.log('The API returned an error: ' + err)
                  return;
                }
                var users = res.users;
                if (users.length == 0) {
                  console.log('No users in the domain.');
                } else {
                  console.log('Users:');
                  for (var i = 0; i < users.length; i++) {
                    var user = users[i];
                    console.log('%s (%s)', user.primaryEmail, user.name.fullName)
                  }
                  response.send(users)
                }        
            })
        })
    })
    

    UPDATE

    The example above is not secure. A Cloud Function, especially with G Suite Domain-wide Delegation, should not respond to http requests unless they come from your application. See in this example that the Cloud Function uses admin.auth().verifyIdToken(idToken)... to validate that the request is authenticated by Firebase.

    If you don't properly handle your G Suite DwD Cloud Function, you risk exposing your G Suite API to the public.