How to Guide: Create a JWT for Google Service Account

I spent over a week trying to create a JWT for a Google Service Account. I already documented the answer on Stack Overflow, but I wanted to post it here as well for anyone who has similar issues.

I’m working on a free plugin to handle this and I’ll update when it’s completed. In the mean time, the instructions are here:

2 Likes

NEW LINK: Google Service Account Plugin - Bubble Plugins

HERE’s WHAT THE STACK POST SAID BEFORE IT WAS DELETED:
I spent the past week and a half trying to figure this out so I thought I would post it below for anyone who needs it.

This will allow you to authenticate a google service account and return the assertion needed to get a bearer token (to use in API calls).

1.) Make a service account and delegate domain wide access to the scopes you need. Google’s docs are actually pretty clear on this. Make a service account Using OAuth 2.0 for Server to Server Applications  |  Google Identity  |  Google Developers domain wide authority: Using OAuth 2.0 for Server to Server Applications  |  Google Identity  |  Google Developers.

Make sure to download a .P12 key for the account and make note of its password. Then go to this website (SSL Converter - Convert SSL Certificates to different formats) and convert it into .PEM, you’ll be asked for the password.

This .PEM file will be needed in the below code. In VisualStudio, all I needed to do was place it in the working directory.

2.) Use the following code to get the signed JWT needed to request the bearer token.

var inputServiceEmail = 'something@service-account-holder-296723.iam.gserviceaccount.com'; //use your service account's email
var inputScope = ['https://www.googleapis.com/auth/admin.directory.group.readonly']; //use your scopes here
var inputKeyFile = 'key.pem'; //you'll need to download a P12 key and convert to pem using this website https://www.sslshopper.com/ssl-converter.html
var inputImpersonate = 'super-admin@email.com'; //optional, but it helps to always impersonate a superadmin on your domain when making requests (not always needed, but for me I always ran into barriers when not using this field)

const crypto = require('crypto');
const GOOGLE_OAUTH2_URL = 'https://oauth2.googleapis.com/token';

const options = {
    // use the email address of the service account, as seen in the API console
    email: inputServiceEmail,
    // use the PEM file we generated from the downloaded key
    keyFile: inputKeyFile,
    // specify the scopes you which to access
    scopes: inputScope,
    // impersonate a super-admin for request
    delegationEmail: inputImpersonate
}

function getToken(options) {

  var iat = Math.floor(new Date().getTime() / 1000);
  var exp = iat + Math.floor((options.expiration || 60 * 60 * 1000) / 1000);
    
  var claims = {
        iss: options.email,
        scope: options.scopes.join(' '),
        aud: GOOGLE_OAUTH2_URL,
        exp: exp,
        iat: iat
  };
    
  if (options.delegationEmail) {
        claims.sub = options.delegationEmail;
  }

  var JWT_header = new Buffer(JSON.stringify({ alg: "RS256", typ: "JWT" })).toString('base64');
  var JWT_claimset = new Buffer(JSON.stringify(claims)).toString('base64');
  var unsignedJWT = [JWT_header, JWT_claimset].join('.');
        
  return unsignedJWT; //returns an unsigned JWT
}

//Generate an unsigned token
var unsignedToken = getToken(options);

//Generate a signed token
function signToken(options, unsignedJWT) {

  var fs = require('fs');
  var crypto = require('crypto');

  var key = fs.readFileSync(options.keyFile);

  
  var JWT_signature = crypto.createSign('RSA-SHA256').update(unsignedJWT).sign(key, 'base64');
  var signedJWT = [unsignedJWT, JWT_signature].join('.');
  return signedJWT;
}

var signedToken = signToken(options,unsignedToken);

console.log(signedToken); //Replace with return statement if you put inside a wrapper-function

You can put this code into VisualStudio and it will give you the token in the console. Otherwise, you can put all of this into your own function and have it return the value. I //'d it in the last line of code.

Now, you can use the console.log(signedToken) or the return value to make an API request to Google. The authentication is self-handled in the header.

POST https://oauth2.googleapis.com/token with two headers: grant_type [urn:ietf:params:oauth:grant-type:jwt-bearer but url encoded as necessary; my program does this for me, but if yours does, use urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer] assertion [your jwt from the console.log(signedToken) or return in wrapper function]

If everything works, you should get an authorization: bearer token in response. You can use this bearer to make your own API calls to google.

1 Like