Boost your Worker development experience with Miniflare Part II

Boost your Worker development experience with Miniflare Part II

In this Article

Overview

In this second part of the tutorial I am going to implement the worker backend, run it locally using Miniflare and deploy my serverless webapp to Cloudflare. There is quite a bit to cover so let’s get started.

Create A Github OAuth application

I am going to create two Github OAuth applications one for my dev environment and another one for my prod environment. This is what the authentication flow will look like:

To create a Github OAuth app you can follow this github doc. For my Dev app I am going to use the following:

I want my callback url to be localhost:8787 which is where my worker is going to run locally. This is where Github is going to redirect to after the initial authorization request. Once I have registered the oauth app, I am going ahead and generating a new client secret. Finally I am noting down my client id and secret. I will need them shortly. Keep in mind you need to generate another oauth application for your production environment where you will use your worker url as callback and your pages url as the website url. I will do that later when I get my worker and page app urls.

Setting up the Worker

I generally like to create my boilerplate manually, however for the sake of keeping this tutorial short and sweet I am going to use the wrangler cli to bootstrap my worker project. I am going to use version 1.19.12 in this example. For this reason I am going to implement a Service Worker rather than the newer Module Worker. You can find out more about the differences here. To install the wrangler cli:

$ npm i @cloudflare/wrangler -g 

Now I am ready to boostrap my worker:

$ cd packages
$ wrangler generate worker https://github.com/cloudflare/worker-typescript-template

Feel free to take time to check out each individual file. I will change the worker/package.json name, mark the package as private and add a script to serve my worker locally.

{
  "name": "@github-login-app/worker-typescript-template",
  "private": "true",
  ...
  "scripts": {
    ...
    "serve": "miniflare --build-command 'yarn build' --watch --debug"
  }
}

Time to add Miniflare, from the root folder:

$ yarn
$ yarn workspace @github-login-app/worker-typescript-template add miniflare -D

Before jumping into the implementation I am going to make some small changes to the wrangler.toml file. I am going to add some dev environment configurations:

name = "coderpunktech-worker"
type = "javascript"
zone_id = ""
account_id = ""
route = ""
workers_dev = true
compatibility_date = "2022-05-04"

[build]
command = "npm install && npm run build"
[build.upload]
format = "service-worker"

[vars]
CLIENT_ID = "<oauth-dev-client-id>"
CLIENT_SECRET = "<oauth-dev-client-secret>"
CLIENT_URL = "http://localhost:8080"

[dev]
ip = "0.0.0.0"
port = 8787
local_protocol = "http"
upstream_protocol = "https"

Implementing the Worker

The wrangler cli generated an index.ts and a handler.ts. I want to modify those files to add the github login implementation. First I am going to the index.ts file and add a typescript declaration for the environment variables I have added to the toml file.

import { handleRequest } from './handler'

declare global {
  const CLIENT_ID: string
  const CLIENT_SECRET: string
  const CLIENT_URL: string
}

addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request))
})

Then in the handler.ts file, I am going to read the code returned by the oauth login authorization call from github, perform a request to github to get an access token and redirect to my frontend application with a set cookie header:

export async function handleRequest(request: Request): Promise<Response> {
  const url: URL = new URL(request.url)
  const code: string | null = url.searchParams.get('code')
  
  const response: Response = await fetch(`https://github.com/login/oauth/access_token?client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&code=${code}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      accept: 'application/json'
    }
  })

  // read the body so I can extract the bearer token
  const body: any = await response.json()
  // prepare the cookie
  const newCookie: string = `bearer=${body.access_token}`

  // redirect to the client application with the cookie
  return new Response('', {
    status: 301,
    headers: {
      location: CLIENT_URL,
      'Set-Cookie': newCookie
    }
  })
}

Before I can run the application locally, I will add the Github OAuth clientId to the href in the GithubLogin.vue button I left empty earlier. So that now looks like this:

  <b-button 
    size="is-large"
    tag="a"
    href="https://github.com/login/oauth/authorize?client_id=<your_github_oauth_app_client_id>">
    <font-awesome-icon icon="fa-brands fa-github" />
    Login with GitHub
  </b-button>

Alright let’s see this in action! To spin up the worker locally I am going to run yarn serve in my worker project and try it out. First I see:

After clicking the login button I am prompted to authorize the Github app I have previously created.

And here are my public repos:

I will be replacing the clientId value in my GithubLogin.vue component with my production oauth app clientId after deploying the application. This is because Cloudflare will generate some unique urls for me. I am also removing my clientId and secret from the toml file. I will set those variables manually in the Cloudflare dashboard.

Deploy to Cloudflare

As previously stated, I am going to use Cloudflare Pages to host my vue application and build it on every push to the master branch. For publishing the worker I am going to setup a github workflow. For the workflow to work I need to generate a Cloudflare api key with edit workers permission and save that as a github secret named CF_API_TOKEN. I will need to save another secret named CF_ACCOUNT_ID. I can get my account id via the wrangler cli:

$ wrangler login # opens the browser and prompt for authorization
$ wrangler whoami # will print the account id

Now I will add a .github/workflow/deploy.yml file in the project root folder that looks like this:

name: Deploy Worker to Cloudflare

on:
  push:
    branches:
      - master

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    name: Build and Deploy
    steps:
      - uses: actions/checkout@v2
      - name: Publish
        uses: cloudflare/[email protected]
        with:
          apiToken: $
          workingDirectory: 'packages/worker'
        env:
          CF_ACCOUNT_ID: $

All I have left to do now is to push my local changes to the Github repository and the worker will be deployed. While the worker deploys, I will get the Vue application setup on Cloudflare Pages. I can easily do that from the cloudflare dashboard then navigating to pages. I will select my Github repository, then the vue preset and make some changes to the settings. For Cloudflare to access Github Repository it requires you to install the Cloudflare Github App to your Github Apps in the settings page in Github.

At this stage both my worker and webapp have been deployed successfully to cloudflare. Now I can go ahead and create my production Github OAuth app using the correct urls and configure the environment variables to the worker settings.

That’s it! And I love it! Cloudflare serverless solutions are really geared towards enabling developers to focus more on building without overheads. You can find the complete source code for this article here.