Authless

Do you know who I am!?!

Repo - Demo

Why

If the popularity of firebase and its offshoots Supabase and AppWrite are anything to go off, simple authentication schemes for apps are always in demand.1 I could make a lot of hand-wavy explanations for why this random bit of software was made: I wanted somethign Fast, Privacy Perserving, infinitely scalable, globally present, high availability... 🥱. Really it came down to the fact that I wanted a way to sign users up that didn't require 3rd party logins or getting more data than I need. Also, I really didn't want to have to manage a server because its very unlikely to get anyone signing up. So, I made authless: a dead simply way to generate authenticated tokens for your user, and do it in such a way that its always really close to them for free.

How

It's implemented entirely with stateless CloudFlare (CF) workers using their globally available KV store, also from Cloudflare. Every cloud provider and their dog has a Cloud Functions service at this point, but for cold start latency CF Workers seemed one of the better options. They also have native Rust support2 which was a huge plus for me because I've been wanting to learn more of a compiled language. Because its meant to preserve privacy3 you dont know if someones made multiple accounts, so be wary of cost-overruns from this. This method of signing up seems to work fine for Hacker News and you'll need somewhere in the neighborhood of 3 Million accounts in a month to overrun the free tier. I've got some plans for some challenge/response that might make it harder for people to exploit your site, but they are as-yet unimplemented.
Cloudflares KV Storage is a lovely key-value store for cloud workers, which just means we get an 'infinitely' big, free, in memory hashmap to store our users in. Its a key value store, so you can only technically get one value per user. Fret not! Our 1 'value' can be a nicely serialized JSON string that we deserialize into a user upon request. In it we'll have basic information about the user, and their most recently generated JWT. If you have a more complicated website that needs a lot of information stored, the limit for KV object sizes is 10mb, so you can almost certainly store whatever you need in there. The real shortcoming is when it comes to querying the KV store: as far as I know, you can only run queries on the keys, not the values, so you'll be searching user-by-user for most data queries.
I initially wanted to use my favorite password hashing library -- argon2 -- for authless. It was, however, not meant to be. Argon2 is a really good password hasher, but part of its security lies in that the password hashes are not fast to generate. This stops attackers from quickly testing the hashes for all the password in your database, but also stops us from using it, as it would push us over the execution time limit on free tier workers by a factor of 7.4 Instead, I opted for a nice salt+cryptographic hash combo, the latter of which being provided by the STHash crate. Its a native rust crate (not a wrapper), faster than Blake3, and seems neat. In terms of cryptographic security, this is strong enough to resist all common attacks. It fails in making it hard to compute the hashes, but I think its important to note that attackers aren't going to try and brute force passwords for the intended userbase of this project.
Once a user is authenticated they will recieve a Json Web Token (JTW) that the site owner can use to display their name, create sessions, and validate who the user is. Once again my initial library choice, in this case the popular jsonwebtoken crate, was not up to the task. I think a portion of its cryptography library was not ready to be used with a wasm-unkown-unknown target, so it would complain during runtime that one of the functions it was trying to call was missing. I opted instead for thejwt-compact crate, which actually has a full test suite for WASM targets! I decided to use Ed25519 elliptic curve cryptography for no other reason than it was asymmetric and more secure than an equal sized RSA key. I leave the generation of a seed value up to the user of this library, you could make it a static environment variable, but no matter how I tried to do that, the CF worker never seemed to provide the value.

Performance

To stay within the free tier, your CF workers execution time needs to stay under 10ms. With Rust thats plenty of time to do your authentication in if you choose your crypto right. My final mean execution time is around 5.6ms, though that varies by route. There's no limit to scalability either as far as I'm aware, just stay below 100K invocations per day in the free tier5.

Challenges

You can take a lot of things for granted when you're developing on a mature architecture like x64 or arm that are just painful to work around within wasm-unknown-unknown. Because the Rust compiler cant assume anything about the execution environment its going to be running in, it has to make sure that its not going to run into any errors at runtime because a function call it linked against is missing. Sure, forego the confusingly complicated cryptography libraries that are linked against processor specific optimizations. But have you ever really thought about time? Yes, even time depends on knowing which little bits in each processor archtitecture represent how much time has elapsed. Alright, maybe you dont need to do time anything.... but what about every single item in your dependency tree. I'm not really experienced enough in rust to know if theres a better way, so it quickly became an execising in finding niche crates that did what I want without having any non-wasm friendly includes.