Authentication Part 4.875

Tue Aug 25, 2020

CLJ in Practice

I finally got around to using clj in a prototyping context. And it's going relatively smoothly so far. My only real complaint is that I seem to have to put

(named-readtables:in-readtable clj:syntax)

at the top of every file where I want to use my cool new map/set literal syntax. I'm hoping there's some way to fix this by just putting it at the top of a package file or something, but that naive solution doesn't seem to work. At first glance, there doesn't seem to be a way to express "load this project with a given, non-default readtable", and I'm not entirely sure why yet.

Return to cl-vote

The project I put some work into is an old piece of arcana from the earlier days of the Toronto CS Cabal. A simple voting system to help us decide what we're reading in a given week. The next step I'm going to take is implementing the actual voting. Step one was just the authentication system.

So here's the deal. Passwords suck, public keys aren't really being used widely for website/app authentication, and that doesn't seem to be something I can easily change. Authenticator apps and 2FA are propagating though. For low-security-requirement situtations, one plausible alternative to passwords is just using that authenticator. So, like, 1FA. The current state of cl-vote is an implementation of such a system in Common Lisp.

The workflow looks like this:

  1. You register by picking a user name that hasn't already been picked.
  2. The system instantly sends you to a screen that displays a QR code compatible with FreeOTP or Authy or whatever
  3. When you want to log in later, enter your username and your authentication code

That's fairly simple. There's no need to remember passwords, though you do now need your phone or authenticator app/browser plugin/what-have-you.

Considering Humane Interfaces

During the construction of this, I briefly considered taking the Raskin approach of letting users log in with just their "password"s. Mechanically, this would involve iterating through the entire user database in order to find if there's anyone whose next code matches the input at login. I decided against it for three reasons

A user name solves enough problems that I'm content burdening users with the task of picking one.

Considering Further Security

Once I combine it with some form of hammering protection, this system is resistant to the sorts of guessing attacks that plague password systems. It's still not resistant against server database breaches. Granted, this particular one is tricky to crack in that way because it's immune to injection attacks as a result of its' data storage model 1, but that's cold comfort. If you did manage to expropriate a user record, you'd gain access to that users' shared secret and could thereafter generate correct solutions for their account at will.

That's sort of the point.

One thing I could do, as a web app proprietor, is keep client fingerprints around and be a bit more cautious about logins coming from devices that a user hasn't used before. It's not entirely clear to me what to do if I detect an anomaly. I guess one thing I could do is request a challenge answer through a different contact method. Like an SMS sender or email, to which I would send a challenge generated by a session-specific secret key and then expect a response.

Doing that would also effectively mitigate the database expropriation attack. It wouldn't mitigate a successful server takeover, but I'm not sure there's a reasonable way to mitigate that at all yet. This might be good enough.

Considering Account Recovery

Account recovery codes are a thing that 2FA systems use to "make" "sure" that a user can still get into their account if they lose their phone/authenticator token/whatever. The way this works is by having the user write down a bunch of codes, each of which can presumably be used for a one-time entry into the system without other authentication methods being available. Cool, I guess. I haven't had to use them yet, and I suspect the sorts of systems I'm planning to build lend themselves more easily to the "make a new account" recovery path than this, but it might still be worth doing.

Mechanically, this means generating some number of alphanumeric codes that are either easy to write down or easy to remember. Then giving the user a workflow where they can enter one of these codes, at which point they are logged in but the code they used is marked as expired.

I'm going to try to implement a couple of these extras, then get bored and move on to the main point.

Which is collective decision making.

  1. And also the "Who would actually try to hack a Common Lisp app" thing. There are definitely lower hanging positions that bear more fruit.

Creative Commons License

all articles at langnostic are licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License

Reprint, rehost and distribute freely (even for profit), but attribute the work and allow your readers the same freedoms. Here's a license widget you can use.

The menu background image is Jewel Wash, taken from Dan Zen's flickr stream and released under a CC-BY license