Recently I had the opportunity to build a small application that needed to authenticate and authorize a user using Google’s sign-in mechanism, and requests on their behalf data from a Google API.
I choose to implement this as a Cloudflare Worker as a serverless compute service leveraging Cloudflare key-value storage (KV) for session storage. The tooling from Cloudflare (wrangler) has evolved nicely since my first attempt at Cloudflare Workers, so I thought it was a high time that I gave it another try.
As any good software engineer would, I started searching for any repository I could use a template to wire up Google OAuth easily. But I failed to find anything that would play nicely with Cloudflare Web worker, or had some proper documentation/tests, or had a decent quality to it. So in this blog post (and in the companion GitHub repository jazcarate/cloudflare-worker-google-oauth), I want to document, explain and go over some interesting decisions so someone just like me would have this to springboard their development.
That being said, feel free to yoink any or all of the code from the repo.
Result
First of all, this is what we’ll develop: An app that can display and filter a user’s Drive files; and provide a link to them.
I choose Google’s Drive listing API as an excuse. Everything we’ll see from now on can be easily changed to use any of the myriad of Google APIs, as they all require roughly the same authentication and setup.
Structure and Systems
To decouple the logic from external sources (such as Google), the project is best understood as a slim entry point index.ts, the core business logic in the handling method (handler.ts), and every external dependency in the lib/ folder.
The main interface from handler.ts is a function that injects all the systems
This not only helps separate concerns but allows us to mock external dependencies in the tests.
Initial request
Once we have all systems initialized, we do match if the request is an /auth callback. We’ll come back to this section later. If the request is not a callback, then I check if the user is authenticated. There are several ways in which a user might not be.
Like if they don’t present any cookies.
const cookies = request.headers.get('Cookie')
if (!cookies) return login(env, google, url)
Or if they have cookies, but not the one we care (auth)
If the fetch returns something, I check the body for errors and panic if needed
const resp = await response.json()
if (resp.error) throw new Error(JSON.stringify(resp.error))
Once Google answers a list of files, then it is just a matter of rendering them. I decided to keep the rendering simple, and inline the whole HTML; but this can be easily adapted to return a JSON, or use a proper template engine. I’ll leave this as an exercise for the reader.
Logout
In the logout case, we revoke the token with google, we remove the auth from the KV and we reply to the client with a deleted cookie.
I leverage the waitUntil lifecycle to respond to the client immediately, and call google and remove the KV in the background. And I use allSettled as I don’t particularly care if the KV couldn’t be deleted, or if Google had trouble removing the token as there is not much more I would be able to do.
Not found
Otherwise, I simply return a status 404 Not Found.
return new Response('Not found', { status: 404 })
Callback
Going back to the /auth request; this needs no authentication (as we are in the process of creating it). So I just check for the contract that Google sign-in documents. The query params has no errors
const error = url.searchParams.get('error')
if (error !== null)
return new Response(`Google OAuth error: [${error}]`, { status: 400 })
And it has a code
const code = url.searchParams.get('code')
if (code === null)
return new Response(`Bad auth callback (no 'code')`, { status: 400 })
If so, we can exchange the code for a proper token via another Google API
Finally, we send the client back to wherever they came from, with their new authentication cookie. We stored the original URL in the state param that Google OAuth allows us to send.
The systems in the lib/ folder are quite straightforward, and can be divided into two subgroups conceptually:
Cloudflare enhanced dependencies
As this application is running in Cloudflare Workers (both the real environment in the cloud and the dev environment generated by npm run dev), some variables are injected into the global scope. These variables are typed in the bindings.d.ts file; and are generated by steps 2 and 3 from README.md#Setup wrangler.
KV module uses the global authTokens variable that Cloudflare Worker injects into the worker. More information about how KV work can be found here.
Env module keeps the environment injectable. Even though we could use the global variables (CLIENT_ID and CLIENT_SECRET) injected to the web worker, this approach allows me to test the handler without having to re-wire global variables; that is a pain.
Utils
And some other systems that are not Cloudflare Worker dependant, but more like utility functions, grouped by their specific domain.
http module has some utils to parse the Cookies header format and build a 302 Redirect response.
Google module has a types API using just fetch.
Crypto module is a small utility to use the crypto API to generate a secure-random string.
If all this is too complicated; you can try out our Identity Management Solution – VYou which was developed by Apiumhub software developers.
Apiumhub brings together a community of software developers & architects to help you transform your idea into a powerful and scalable product. Our Tech Hub specialises in Software Architecture, Web Development & Mobile App Development. Here we share with you industry tips & best practices, based on our experience.