Authentication for Google Cloud Functions with JWT and Auth0

Surprised that there was no built-in authentication mechanism for Google Cloud Functions, I made an attempt to implement a simple one with JWT and Auth0

With all the hype around serverless, I recently took a stab at creating a cloud function and see how it goes. I went with Google Cloud Functions instead of AWS Lambda, because I had some free signup credits on Google.

I started with this tutorial https://cloud.google.com/functions/docs/tutorials/http, and it seemed pretty straightforward. I created a cloud function with a HTTP trigger in about 30 minutes.

The function I deployed adds an entry to a Cloud Datastore database, and would do so every time I make a curl request to the function’s endpoint. That was pretty thrilling.

curl -X POST -H "Content-Type: application/json" \
-d '{"foo": "bar"}' \
"https://.cloudfunctions.net/post"

However, it soon dawned on me that this is pretty insecure, as anyone who knows of this endpoint could write to the database. Imagine if I wrote a delete function! I thought surely Google must have built in some sort of authentication scheme for Cloud Functions. But after googling around for a while, it didn’t seem so. I did next what any clueless developer would, and post a question on StackOverflow.

After a few days, the answers I got back seemed pretty disappointing. Apparently if I had used AWS Lambda, I could leverage API Gateway, which has support for auth. But I am on my own for Google Cloud Functions.

So I decided to implement an authentication check for my cloud function with a JWT token passed in in the form of an Authorization header access token, with the help of Auth0.

Here’s the implementation in Node, and the explanation is after.

const jwksClient = require('jwks-rsa');
const jwt = require('jsonwebtoken');

const client = jwksClient({
  cache: true,
  rateLimit: true,
  jwksRequestsPerMinute: 5,
  jwksUri: "https://.auth0.com/.well-known/jwks.json"
});

function verifyToken(token, cb) {
  let decodedToken;
  try {
    decodedToken = jwt.decode(token, {complete: true});
  } catch (e) {
    console.error(e);
    cb(e);
    return;
  }
  client.getSigningKey(decodedToken.header.kid, function (err, key) {
    if (err) {
      console.error(err);
      cb(err);
      return;
    }
    const signingKey = key.publicKey || key.rsaPublicKey;
    jwt.verify(token, signingKey, function (err, decoded) {
      if (err) {
        console.error(err);
        cb(err);
        return
      }
      console.log(decoded);
      cb(null, decoded);
    });
  });
}

function checkAuth (fn) {
  return function (req, res) {
    if (!req.headers || !req.headers.authorization) {
      res.status(401).send('No authorization token found.');
      return;
    }
    // expect authorization header to be
    // Bearer xxx-token-xxx
    const parts = req.headers.authorization.split(' ');
    if (parts.length != 2) {
      res.status(401).send('Bad credential format.');
      return;
    }
    const scheme = parts[0];
    const credentials = parts[1];

    if (!/^Bearer$/i.test(scheme)) {
      res.status(401).send('Bad credential format.');
      return;
    }
    verifyToken(credentials, function (err) {
      if (err) {
        res.status(401).send('Invalid token');
        return;
      }
      fn(req, res);
    });
  };
}

I use jwks-rsa to retrieve the public key part of the key that was used to sign the JWT token, and jsonwebtoken to decode and verify the token. I use Auth0, so jwks-rsa reaches out to the list of public keys to retrieve them.

The checkAuth function can then be used to safeguard the cloud function as:

exports.get = checkAuth(function (req, res) {
  // do things safely here
});

You can see the entire Google Cloud Functions repo at https://github.com/tnguyen14/functions-datastore/

The JWT / access token can be generated in a number of way. For Auth0, the API doc can be found at https://auth0.com/docs/api/authentication#authorize-client

Once this is in place, the HTTP trigger cloud function can be invoked with:

curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Bearer access-token" \
-d '{"foo": "bar"}' \
"https://.cloudfunctions.net/get"
comments powered by Disqus