Back to Feed
article 4m read

One sign-in box, two doors, and a hint I only half fixed

The scheduling app's sign-in box now routes email users to emailed codes and everyone else to short PINs, and rereading the change turned up a reset hint I'd reworded without closing, a session merge that changed a security marker, and a redirect check a double slash walks past.

Article DepthPractice

Flow

PIN sign-in, step by step, with its weak spots

  1. 1Pick a door

    Works, but answers differently for admin names and freshly reset names.

  2. 2Check the PIN

    Salted slow hash and constant-time compare; a stolen table is still easy to guess through.

  3. 3Lock

    Per account only, so anyone who knows a name can lock that person out.

  4. 4Make the session

    One shared routine for every door, but one path now marks a second step done for everyone.

  5. 5Send to next page

    Only checks for a leading slash, so a double slash can send you to another site.

Some of the people who use my staff scheduling app don't have a work email, so an emailed code was useless to them. I put two doors behind one sign-in box. Reading the change back, most of it holds up. A few spots don't.

How the box picks a door

The browser sends what you typed to the server. If it looks like an email, you get an emailed code, and the server doesn't look anything up. If not, it's a username and you get a PIN screen. Usernames can't contain an at sign, so the two never get mixed up. Admins can't use the PIN door at all, and the server checks that twice. Managers can.

The catch is the answer itself. For usernames, it comes back one way for ordinary and unknown names, another for admin names, and a third for names with a fresh reset. Two of those tell a stranger something about a real account.

The PIN and the lock

PINs are short, salted, slowly hashed, and compared in constant time. That's the easy part. Short PINs are still easy to guess if someone steals the hashes, so the real defense is the lock: after a handful of wrong tries, the account stops taking PINs for a while.

That lock is per account. It stops someone hammering one person. It does nothing about one common PIN tried on many names, and it lets anyone who knows your username lock you out on purpose. If you build this, add a limit per source too, and give admins an unlock button.

The reset hint

A reset sets a flag. My first PIN screen said, out loud, that an admin had recently reset your PIN. Anyone typing your name saw it. Changing the words to a neutral prompt left the condition alone.

The words are neutral now, but the prompt still appears only for accounts that were just reset.

So the prompt still shows only for reset accounts, and the flag still sits in the server's public answer. Show everyone the same prompt and keep the flag private until a correct PIN. Also, nothing in this change makes the person actually pick a new PIN.

One way to make a session

Email and single sign-on used to build sessions separately. Now every door uses one shared routine, so cookie changes reach all of them. But single sign-on used to mark the second step as done only for people without one. Now it's marked done for everyone. I haven't checked whether anything trusts that mark.

A hidden form and a loose redirect

The PIN screen posts a hidden form so the server can set cookies and redirect in one go. Two problems. A failed try puts the username in the address bar, which ends up in logs. And the "go here next" value only has to start with a slash, so a double slash, which a browser reads as another site, gets through. Allow only a short list of known pages.

Same handle, two workspaces

Usernames are unique inside a workspace, but sign-in doesn't ask which workspace. Two workspaces with the same name means sign-in picks one. Make handles unique everywhere.

Temporary PINs, resets and placeholder accounts

Each person gets an email or a username. Temporary PINs come from a plain random generator; use a secure one. Resets are logged but don't end old sessions. Old placeholder accounts get a username on their first reset, and the staff panel shows it. Role permissions also changed in the same commit, which should have been its own change.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward