Using Rust Cloudflare Workers: Serverless hCaptcha
Tags: rust, serverless, cloudflare, webdev
Published: 2021-09-29 10:06:54

---
title: "Using Rust Cloudflare Workers: Serverless hCaptcha"
published: "true"
description: "Using Rust Cloudflare Workers: how to run fast and secure Rust code from a Cloudflare Worker endpoint - hCaptcha serverless function example."
tags: "rust, serverless, cloudflare, webdev"
canonical_url: "https://rodneylab.com/using-rust-cloudflare-workers/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7yz1t6msd8tn43khqw9.png"
---

## ☁️ What are Cloudflare Workers and Why use Them?

We'll start this post on using Cloudflare Workers in Rust by taking a quick look at what workers are.  Next we'll see why  you might want to write Cloudflare Workers in Rust.  Then we will build out a full serverless function which you can use for verifying front-end web users with hCaptcha.  If that sounds exciting, then let's crack on!

![Using Rust Cloudflare Workers: Test Route: Free plan details: 100k requests per day, up to 10 ms CPU tiem per request, lowest latency after the first request"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74y9w7sl77jtix3z1j8u.png)

Cloudflare Workers provide a way to run serverless code with a free tier.  Serverless means you do not have to provision and maintain the server yourself.  The provider scales up capacity as and when neeed. Cloudflare workers are akin to Netlify serverless functions but work differently.  They start up quicker than other serverless solutions.  Have you been to a quiet terminal in a small airport which has escalators that only start up when they detect a user wants to step on?  This is kind of how most serverless environments work.  You have to wait a moment as the server spins up and is ready to process your request, just like it takes the escalator a moment to start moving. Cloudflare workers are like escalators at a busy airport, they are always running, ready for someone to step on.

### What Makes Workers Fast?

Workers are intrinsically faster because there is no startup time. You just drop your code (<a aria-label="Read more about isolates from Cloud flare" href="https://developers.cloudflare.com/workers/learning/how-workers-works">as an isolate</a>) into a WebAssembly context capable of running hundreds of other isolates simultaneously and it runs straight away.  They <a aria-label="Read about Cloud flare Worker speed" href="https://blog.cloudflare.com/cloudflare-workers-the-fast-serverless-platform/">run almost three times faster than Lambda functions</a>.  The Cloudflare network itself helps in reducing running time, with the request arriving quick at your endpoint.  Cloudflare Workers run on the V8 WebAssembly engine.

On a free tier account you can run the worker up to 10ms.  Although that might sound quite stingy, I found no issues running hCaptcha requests in this time.  Here's some output from the dashboard showing CPU time per execution.  I was running three Supabase queries on each request as well as the hCaptcha verification on the most intensive requests here:

![Using Rust Cloudflare Workers: Test Route: C P U time. Chart shwowing C P U time, 50th percentile is 2.3 ms 75th percentile is 3.2 ms, 99th percentile is 7.3 ms, 99.9th percentile is 7.4ms](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jb2vhq3axudizp2qdfoq.png)

## 🔥 Why Write Cloudflare Workers in Rust instead of JavaScript?


<a aria-label="Read more about Rsut support in Cloud flare workers" href="https://blog.cloudflare.com/workers-rust-sdk/">Native Rust support has only just been introduced into Cloudflare Workers</a>, which is pretty exciting!  Rust is a modern language which  <a aria-label="Read more about Rust from Figma" href="https://www.figma.com/blog/rust-in-production-at-figma/">offers best-in-class speed with low resource usage</a>.  Typically it runs faster that JavaScript (partly) because it is compiled ahead of time so does not need to be interpreted at runtime.  One drawback is that this comes with the trade-off of development being slightly slower as each change has to be compiled before it can be tested.  Rust also assists in writing more secure code than many other languages. This security also comes at a cost as the compiler has to perform some extra checks.

Finally any crates you use with Rust Workers need to be able to compile to Web Assembly Language (WASM).  It's not all doom and gloom as the speed and security offer significant potential over JavaScript.  You can also compile C or C++ into WASM so might consider using those as alternatives to Rust and JavaScript in Workers.

## 🧱 What we're Building: Serverless hCaptcha Verification

hCaptcha offers a similar service to <a aria-label="Open post on using serverless Google reCAPCTCHA" href="https://rodneylab.com/gatsby-cloud-functions-recaptcha/">Google reCAPTCHA</a>.  Both of these services are designed to give you some confidence that the user interacting with your site is actually a person and not a bot.  As an example you might use hCaptcha on a contact form to filter responses submitted by bots.  When the user submits the form, code running in the background will ask the user to complete a challenge. On successful completion of the challenge, we can let the user complete the form submission.

There are two parts to the verification process.  In the browser, your code will submit the challenge response to hCaptcha with your site key.  hCaptcha replies with a response key.  That response key is needed in the second part.  The second part is usually performed on a server.  We will be using Rust Cloudflare Workers though to verify serverlessly (if that's a word)!  We'll focus on that second part here.  So we will need to listen for a verification request from our site, on a route we provide.  Then we contact hCaptcha with our secret key and the user response key.  If hCaptcha than verifies the user is a not a bot, we can proceed with processing the requested action. This might be sending an email to a site admin with the submitted contact form details.

Now we know what we're doing, why don't we set up out worker environment?

## 🧑🏽‍🎓 How to Get Started Using Rust Cloudflare Workers

### Preliminaries

We'll start by setting up our dev environment, before creating, deploying and testing our worker.  You can follow along even if you are new to Rust, I will provide a little extra explanation to help here.  If you don't already have Rust dev tools set up, just <a aria-label="See recommended rust up installation instructions" href="https://www.rust-lang.org/tools/install">head over to the official Rust site for the recommended one-line terminal command</a> to get that up and running.  Also include `~/.cargo/bin` in your PATH environment variable (see previous link).

### `wrangler` installation

Cloudflare have developed `wrangler`; a command line interface (CLI) tool written in Rust to assist in worker development.  Official documentation suggests installing wrangler with `npm`.  I got on better using `cargo` from the Rust toolchain.  I'll show you that way, but keen to hear your feedback on which works best for you.

OK lets install `wrangler` with cargo (the Rust package manager):

```shell
cargo install wrangler
```

this will take a few minutes to download all the dependencies needed and then build `wrangler`.  Alternatively <a aria-label="Open docs on installing wrangler with n p m" href="https://github.com/cloudflare/wrangler#install-with-npm">use npm as per the official documentation</a>.

That's the environment set up.  Next we'll create our project.

⚙️ Creating a new Project for Using Rust Cloudflare Workers

Firing up a new project is as simple as using the command below.

```shell
wrangler generate --type=rust hcaptcha-serverless-rust-worker
cd hcaptcha-serverless-rust-worker
```

Ignore any errors about the recommended type.  The command creates all the boilerplate we need. Let's have a quick look through the generated files.

### Project Structure

```plaintext
.
├── CODE_OF_CONDUCT.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE_APACHE
├── README.md
├── src
│   ├── lib.rs
│   └── utils.rs
└── wrangler.toml
```

- `Cargo.toml` - the usual Cargo configuration file.  <` aria-label="Read more about Cargo" href="https://doc.rust-lang.org/cargo/guide/why-cargo-exists.html">Cargo is Rust's package manager</`> and we list any crates (dependencies) our project needs in here.

- `src/lib.rs`: this is the file that does our heavy lifting. We will define the hCaptcha verification function in here and create a route we can send verification requests to from our client front end.

- `wrangler.toml` - a worker configuration file, we can list environment variables in here (like you might do in a `.env` file in a JavaScript project).  There is a different mechanism for handling secret variables which we will come to later.

### Initial Test

Let's test the example code out before continuing and adding our own code.  The first time we build will take a little bit longer as we have to compile all the crates from scratch.  Let's do it now:

```shell
wrangler build
```

You should get a message saying `✨  Build completed successfully!` once complete.  Next we will link the worker to our Cloudflare account.  If you <a aria-label="Create a new Cloud flare account" href="https://dash.cloudflare.com/sign-up">don't yet have a Cloudflare account, you can set one up for free</a>.

Now <a aria-label="Open the Cloud flare dashboard" href="https://dash.cloudflare.com/login">open the Cloudflare dashboard</a>, logging in if necessary.

#### Linking Local Environment to your Cloudflare Account

Once you have logged in, in the terminal in our project folder type the command:

```
wrangler login
```

Answer &ldquo;yes&rdquo; to open the link in your browser. You will need to accept the prompt to be able to use wrangler to create your worker.  Back in the terminal you will see a message telling you wrangler is successfully configured (if everything went well).  The command creates a `.wrangler` folder. Be sure to add this to you `.gitignore` file so it is not committed if you push your project to GitHub.

We are all ready to fire up the example code now!  The final command in the launch sequence is 

```
wrangler dev
```

This is pretty much like the dev environment when working on a Next, Node or SvelteKit app.  When you make changes the code will automatically compile and create an updated version of the binary.  By default wrangler dev serves the worker at <ExternalLink aria-label="Open the dev worker" href="http://127.0.0.1:8787">127.0.0.1:8787</ExternalLink>.

#### Environment Variables

Let's see what we can do! Jump to `src/lib.rs`:

```rust
        .get("/worker-version", |_, ctx| {
            let version = ctx.var("WORKERS_RS_VERSION")?.to_string();
            Response::ok(version)
        })
```

This block defines a route `/worker-version` which responds to `GET` requests.  In line `50` we reference the `WORKERS_RS_VERSION` variable.  You can see this is defined in line `8` of `wrangler.toml`.  You can create your own varaibles there and use them in your code in the same way.  Let's test our worker.  Go to <ExternalLink aria-label="Open the dev worker" href="http://127.0.0.1:8787/worker-version">127.0.0.1:8787/worker-version</ExternalLink> in your browser.  This won't be the most exciting web page you look at today! Nonetheless, it contains the defined variable.

![Using Rust Cloudflare Workers: Test Route: browser screenshot with the address http://127.0.0.1:8787/worker-version in the navigation bar.  Only 0.0.6 is displayed in the main window](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pt706pjekj31x36sj8rl.png)

You have successfully tested your first Rust Worker!  Next, let's code up our hCaptcha endpoint.

## 🖥 Coding up our Rust Cloudflare Worker


First we will need a function to process the request sent from our client website and interact with hCaptcha, verifying the user for us.  Add the following to `src/lib.rs`:

```rust
async fn verify_captcha(client_response: &str, secret: &str, sitekey: &str) -> Option<bool> {
    let mut map = HashMap::new();
    map.insert("response", client_response);
    map.insert("secret", secret);
    map.insert("sitekey", sitekey);
    let client = reqwest::Client::new();
    let response = match client
        .post("https://hcaptcha.com/siteverify")
        .form(&map)
        .send()
        .await
    {
        Ok(res) => res,
        Err(_) => return None,
    };
    match response.json::<HcaptchaResponse>().await {
        Ok(res) => Some(res.success),
        Err(_) => None,
    }
}
```

Here we get the client response key and our secret site key as inputs to the function and return a boolean wrapped in an Option. Options are a Rust feature which allow us to return a &ldquo;nothing&rdquo; variable in the case something went wrong &mdash; so we can return `true` or `false` (`Some` results) if we get a regular response from hCaptcha and `None` if we get an error or can't reach the server for some reason.  Lines `17`&ndash;`20` build up the query string needed for our hCaptcha request.  We send that in lines `21`&ndash;`25`.

### hCaptcha JSON Response Struct

hCaptcha will respond with a JSON object.  Because <a aria-label="Read about strong and weak typing on Wikipedia" href="https://en.wikipedia.org/wiki/Strong_and_weak_typing">Rust is a strongly typed language</a> we need to let the compiler know what types the hCaptcha response variables are.  We can do that using the `HCaptchaResponse` struct used in line `31`.  Let's define the struct now:

```rust
// Expected response
// {
//  "success": true|false, // is the passcode valid, and does it meet security criteria you specified, e.g. sitekey?
//  "challenge_ts": timestamp, // timestamp of the challenge (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
//  "hostname": string, // the hostname of the site where the challenge was solved
//  "credit": true|false, // optional: whether the response will be credited
//  "error-codes": [...] // optional: any error codes
//  "score": float, // ENTERPRISE feature: a score denoting malicious activity.
//  "score_reason": [...] // ENTERPRISE feature: reason(s) for score.
// }
#[derive(Deserialize)]
struct HcaptchaResponse {
    success: bool,
}
```

We only use the `success` field from the response, though we have the other variables listed in a comment here for reference.

See the <a aria-label="Open the h captcha docs" href="https://docs.hcaptcha.com/">hCaptcha docs for more on the verification processes and additional data sent in the response</a>.

### Setting up Crates

You might notice <ExternalLink aria-label="Open the reqwest crate docs" href="https://docs.rs/reqwest/0.11.4/reqwest/">we used the `reqwest` crate here to contact hCaptcha</ExternalLink> (equivalent functionality to `fetch` or `axios` in the JavaScript world).  We need to include the crate in our `Cargo.toml` file for our code to work:

```toml
[dependencies]
cfg-if = "0.1.2"
reqwest = { version = "0.11.4", features = ["json"]}
worker = "0.0.6"
serde = "1.0.117"
serde_json = "1.0.67"
```

Also add the `serde` crate which we need too.  Now we are missing a route to receive hCaptcha requests from our client front end on.  Update the `main` function, replacing the routes we no longer need:

```rust
#[event(fetch)]
pub async fn main(req: Request, env: Env) -> Result<Response> {
    log_request(&req);
 
    // Optionally, get more helpful error messages written to the console in the case of a panic.
    utils::set_panic_hook();
 
    // Optionally, use the Router to handle matching endpoints, use ":name" placeholders, or "*name"
    // catch-alls to match on specific patterns. Alternatively, use \`Router::with_data(D)\` to
    // provide arbitrary data that will be accessible in each route via the \`ctx.data()\` method.
    let router = Router::new();
 
    // Add as many routes as your Worker needs! Each route will get a \`Request\` for handling HTTP
    // functionality and a \`RouteContext\` which you can use to  and get route parameters and
    // Environment bindings like KV Stores, Durable Objects, Secrets, and Variables.
    router
        .options("/verify", |req, ctx| {
            preflight_response(req.headers(), &ctx.var("CORS_ORIGIN")?.to_string())
        })
        .post_async("/verify", |mut req, ctx| async move {
            let data: CaptchaRequest;
            match req.json().await {
                Ok(res) => data = res,
                Err(_) => return Response::error("Bad request", 400),
            }
            let hcaptcha_sitekey = ctx.var("HCAPTCHA_SITEKEY")?.to_string();
            let hcaptcha_secretkey = ctx.var("HCAPTCHA_SECRETKEY")?.to_string();
            match verify_captcha(&data.response, &hcaptcha_secretkey, &hcaptcha_sitekey).await {
                Some(value) => {
                    if value {
                        // you would proceed with request here
                        console_log!("User verified");
                    }
                    // We don't let the user know we think they are a bot if verify failed
                    Response::ok("Have a great day!")
                }
                // something went wrong - we don't know if the user is a bot or not
                None => Response::error("Error verifying user", 400),
            }
        })
        .run(req, env)
        .await
}
```

Here you see we created a route with code for handling a `POST` request (we will look at the `OPTIONS` part in a moment).  We expect to receive a JSON object from our client, which looks like this:

```json
{
	"response": "10000000-aaaa-bbbb-cccc-000000000001"
}
```

`CaptchaRequest` in line `72` is a struct analogous to the one we created for the hCaptcha response earlier.  For a real-world app, we might receive form data along with the `response` field from the client.  In lines `77`&ndash;`78`, we access secret variables which we need to identify and authenticate ourselves with hCatpcha.  Let's define these now.  You can <ExternalLink aria-label="Sign up for an h captcha account" href="https://www.hcaptcha.com/">set up a free hCaptcha site to get valid credentials</ExternalLink>.


### Handling Secrets

At the command line type this command to store a secret variable in our worker's environment:

```shell
wrangler secret put HCAPTCHA_SITEKEY
```

You will get a prompt to type in the value.  Repeat for `HCAPTCHA_SECRETKEY`.  Finally add a `CORS_ORIGIN` string.  This will be a comma separated list of allowed origins. If you are testing and your client dev server is at `http://127.0.0.1:3000` and you want to test staging environment at `https://example.com`, enter `http://127.0.0.1:3000,https://example.com`.  We will add the code for this shortly.

Now if you <ExternalLink aria-label="Open the Cloudflare Dashboard" href="https://dash.cloudflare.com/">jump to the Cloudflare dashboard</ExternalLink>, you should see the Cloudflare logo top left.  Click the dropdown just beside it and select **Workers**.  Next find the `hcaptcha-serverless-rust-worker`, click it and open the **Settings** tab, you will see the secrets have been saved.

![Using Rust Cloudflare Workers: Test Route: Cloud flare dashboard screenshot with the names of the environment variable just set up listed within the Environment Variable section.  The values are not shown, in their place is the text Value encrypted](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0cm87xkq100gfmbrn0x.png)


### CORS Preflight Response

Because we will receive JSON on our endpoint, the client browser will likely run a <ExternalLink aria-label="Read about C O R S preflight reqeuest on M D N" href="https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request">preflight CORS check</ExternalLink>.  This will contact the same endpoint as the verify request itself, albeit using the `OPTIONS` method rather than `POST`.  We just need to respond back, with the CORS headers so that the browser will proceed.  Let's add the code for this now and also add missing parts so we can wrap up.

Add `use` statements for the crates we use (these are the Rust equivalent of JavaScript `import` statements):

```rust
use serde::Deserialize;
use std::collections::HashMap;
use worker::*;
```

Define the `CaptchaRequest` struct (include client data, for example form field entries in a real-world app):

```rust
#[derive(Deserialize)]
struct CaptchaRequest {
    response: String,
}
```

Finally, define the `preflight_response` function:

```rust
fn preflight_response(headers: &worker::Headers, cors_origin: &str) -> Result<Response> {
    let origin = match headers.get("Origin").unwrap() {
        Some(value) => value,
        None => return Response::empty(),
    };
    let mut headers = worker::Headers::new();
    headers.set("Access-Control-Allow-Headers", "Content-Type")?;
    headers.set("Access-Control-Allow-Methods", "POST")?;
 
    for origin_element in cors_origin.split(',') {
        if origin.eq(origin_element) {
            headers.set("Access-Control-Allow-Origin", &origin)?;
            break;
        }
    }
    headers.set("Access-Control-Max-Age", "86400")?;
    Ok(Response::empty()
        .unwrap()
        .with_headers(headers)
        .with_status(204))
}
```

This just lets the browser know which types of requests we are happy to receive.

## 💯 Testing

![Using Rust Cloudflare Workers: Test client - h captcha where user must identify pictures with a train. 9 images are shown](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w51hg2f25n7mctn9nkub.png)

To test this all out, you will need to knock up a client app which sends the verify request to our worker. It sends this once it has a client response token from hCaptcha.  In JavaScript, using fetch, the request to our worker might look something like this:

```javascript
  async function handleVerify() {
    try {
      if (browser) {
        const { response } = await hcaptcha.execute(hcaptchaWidgetID, {
          async: true,
        });
        const responsePromise = fetch(\`\${workerUrl}/verify\`, {
          method: 'POST',
          credentials: 'omit',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            response,
          }),
        });
      }
    } catch (error) {
      console.error(\`Error in handleVerify: \${error}\`);
    }
  }
```

## 🙌🏽 Wrapup

In this post we saw:

- why you should consider using Rust Cloudflare Workers,

- creating the Rust worker from scratch,

- how you might handle server user verification using hCaptcha, including responding to preflight CORS requests.

I hope you have found this useful.  If it is your first time looking at Rust code, I hope you like it! Sorry there wasn't time/space to explain more of the Rust code.  I <a aria-label="Open the Rust Programming Language guide" href="https://doc.rust-lang.org/book/">highly recommend you take a look at the Rust book</a>.  It is really well written and arguably the best way to get started in Rust. Let me know if you would like to see more posts on Rust generally or serverless Rust in particular.

<a aria-label="Learn how to use Rust in Netlify serveless functions" href="https://rodneylab.com/use-serverless-rust-with-netlify-functions/">You can use Rust in Netlify functions</a> and in the AWS serverless sphere.    I'm keen to hear how you are using Rust Cloudflare Workers in your own projects.  Drop a comment below or <a aria-label="Mention rodney lab on twitter" href="https://twitter.com/intent/user?screen_name=askRodney">mention @askRodney on Twitter</a>.

The <a aria-label="Open the code on the Rodney Lab Git Hub repo" href="https://github.com/rodneylab/hcaptcha-serverless-rust-worker">full code is on the Rodney Lab GitHub page</a>.

## 🙏🏽 Feedback

Have you found the post useful? Which other hosting service would you like to know how to host a SvelteKit site on? Would you like to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a aria-label="Reach out to me on Twitter" href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter and also <a aria-label="Contact Rodney Lab via Telegram" href="https://t.me/askRodney">askRodney on Telegram</a>. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on svelte kit" href="https://rodneylab.com/tags/sveltekit/">SvelteKit</a> as well as other topics. Also <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://rodneylab.com/about/#newsletter">subscribe to the newsletter to keep up-to-date</a> with our latest projects.