Public Key Auth: Val Town users can be your users

Steve Krouse on

Every public function in Val Town has an API endpoint. Some vals wrap premium APIs (openai, rime) that you can use for free. The idea is that sometimes you want to just try an API or use it a tiny amount (<$0.10) and we’re happy to cover that.

But what about abuse? It’s easy enough to add a rate-limit to these functions to ensure that the total usage is below some maximum, but it would be nice if we could make this rate-limit per-user. If “borrow my API key” becomes a pattern, you could imagine a marketplace of premium functions on Val Town that somehow charge for usage… but we’re getting ahead of ourselves.

First, we need a way for Val Town users to authenticate themselves to third-parties. This is distinct from authenticating to Val Town, the platform. That’s a much easier problem - we provide api auth tokens to authenticate with us. The challenge is authenticating with another Val Town user. You need to prove that you are who you say you are, that you made this very request, and that you’re making it now. The generic way to solve this sort of problem is public key cryptography:

  1. Create a public/private key pair
  2. Publish the public key under your Val Town username
  3. Whenever you want to make an authenticated request, collect the request package:
    1. the endpoint
    2. the data you’re sending
    3. the time you’re making the request
    4. your Val Town username
  4. Sign the package & send along the signature with your request
  5. The API you’re calling can then do the reverse of this:
    1. fetch your public key from your Val Town account
    2. verify the signature against the provided data
    3. confirm the timestamp is recent enough.

It took an afternoon to get it working, and the main issue was struggling to understand the Web Crypto API and get them properly encoded into a format that can persist on Val Town. I learned a lot about encoding ArrayBuffers into base64 and back again. But the whole point of Val Town is that now that I’ve written these vals, you don’t have to worry about any of that - you just call @stevekrouse.generateKeys and viola you get keys in beautiful JSON. Let me take you through how to set yourself up to both authenticate with this system and accept authenticated requests.

1. Generate your keys

Click Run to generate keys and see them in JSON. If you’re logged into Val Town, this will save those keys to your account.

2. Publish your public key

Above we generated a public and private key pair. We want your private key to remain private always but we need to publish your public key.

  1. Go to your public key
  2. Press the 🔒 icon toggle and publish that val

3. Make an authenticated request

To make an authenticated API request you need to pass:

  1. The Val you want to call
  2. The args you want to pass
  3. Your handle
  4. Your keys

@stevekrouse.runValAPIAuth packages all this up with a timestamp, signs it, and makes the request. Here’s how you’d use it:

4. Verify the request

To make an API that verifies the request, you can use @stevekrouse.verifyAPIAuth:

Further directions

This scheme uses the Val Town Run API, but it can easily be adapted to the Val Town Express API. These keys and this scheme works only for signing/authorizing. We could also build an end-to-end encrypted version of this scheme by encrypting the request with the public key of the recipient. One could also imagine naming these schemes and passing your scheme’s protocol name to its recipient so that it knows how to properly authenticate you.

It’d be cool to add payment primitives on top of these authentication ones. One issue is that vals are not thread-safe and thus aren’t a good data store for payments. However this would be a perfect use for a regular sql database in conjunction with Val Town (Neon, PlanetScale, Supabase, etc). An Val Town user could have a whole payment system in their own namespace. Anyone could register a payment with them and then they would follow up with the payer and recipients later (by email?) to actually collect and distribute the payment.

We might one day adopt authentication and payment into Val Town as a first-class primitives, but it’s fun to keep them in userspace for as long as possible to encourage experimentation and innovation.

You may have noticed that Val Town has many of the the best parts of web3 — a global runtime of code and data — but for good and ill, there’s no blockchain. We keep everyone’s data in a handy Postgres instance, which means it’s super fast and cheap, but you’ve gotta trust us at Val Town. We also lack any sort of authentication or payment primitives… but maybe not for long!

Edit this page