Charles Chamberlain

Generic user management

2021-03-25

I like the idea of a simple web app. Something that writes down state from a user and then later renders a view of that state to the user. As a concrete example, think of a note-taking app. User takes some notes, app writes notes to disk, later user looks up and reads old notes. Simple and relatively easy!

Alas things get much more complicated if you want to support multiple users. You need a database, some kind of password-encryption system, and a whole bunch more routes (i.e. /login, /signup, /edit_user, etc.). What's worse, you need to be really confident that the login process is not hackable. I think there might be a better way.

For some background, I am using Caddy as a "reverse proxy" to serve my site. (Among other things, this lets me pick and choose which ports are open to the public and served by which services.) It's wonderful. I simply run a local webserver on port 4444 with ruby main.rb. And then I have the following stanza in my caddyfile (similar to the configuration one would use for nginx or apache):

my.example.org {
  reverse_proxy 127.0.0.1:4444
}

What's special about Caddy compared to other such proxies is that it handles SSL encryption for me. I don't have to know anything about certificate authorities or https-specific headers, but traffic to my site is encrypted with https. Caddy is fully abstracting these things away from me, and I love it. What if it could abstract away user authentication too?

Almost all of these reverse-proxy applications (Caddy, Nginx, Apache server) support "basic auth". That is, restricting access to particular sites to a hardcoded set of users. Typically you give the software a username and a hash of the password you want to use, and then when a new user tries to access the page the browser first asks them to fill in a username and password. In Caddy, this looks like:

basicauth / {
	alice i5iejRMYkF3MFZhZ3VtV3E1SzBWZEZ5Q3VWc0tzOE
}

This is pretty cool, but I have to manually add each new user myself. Instead I would like new users to be able to sign-up, either by creating a new account or logging in with a service like Google or Github. I want this to happen at the proxy level: completely unbeknownst to my app. A new user would first be forwarded to a sign-up page and only after signing up would they be sent to my app. The http request sent to my app would include their authenticated username, so I could serve each user only their own notes. This is what I mean by "generic": user management handled completely separately from my app or any other app for that matter. Sounds doable?

Turns out there is some support for this around the web, in the form of Nginx and Caddy extensions. The one that seems most promising is Caddy Auth Portal, though I'm a little surprised it doesn't seem to have gotten a ton of traction. I'll try it out and report back!

Anyways I'm a bit curious if people have any insights here — maybe there are generic ways to do user management that I don't know about. Definitely reach out if you have anything to share!