Back to Feed
article 3m read

New users got a login minted before their workspace existed

A signup webhook builds the workspace but can never reach the session cookie, so my home page now waits, asks the login provider, and refreshes the session itself, and I still have two doubts about whether that refresh works.

Article DepthPractice

Flow

What the home page does on a first login

  1. 1Read the token

    A workspace means carry on. None means start waiting.

  2. 2Wait for the user row

    Same retrying lookup, with a ceiling on its waits.

  3. 3Ask for memberships

    A few tries, a couple of seconds apart.

  4. 4Refresh the login

    Refresh for that workspace, then send the person home.

  5. 5Out of tries

    A card with reload and sign-out buttons.

New accounts on my admin app would sign up and land on a home page that couldn't tell which workspace they were in. They were logged in. Their login was just made too early.

Two paths race

Signup sends two things at once. The login provider signs the person in and sends their browser back to my app. It also sends my server a webhook, a message saying a new user exists. That webhook is the only thing that creates the user's row, their workspace, and the link between them.

If the browser gets there first, its login token says nothing about a workspace. The webhook landing a moment later doesn't change that token. The token in the cookie stays the same.

The webhook can't fix the login

The obvious move is to have the webhook refresh the login. It can't. The webhook is a server-to-server call, and the refresh token lives in the person's cookie. The webhook can change what the next token will say. It can't hand that token to the browser.

So the webhook code stayed the same and got comments saying why. I think those comments are worth it, since the webhook is the first place anyone would look.

The page fetches it

The person's own request is the only thing holding the login, so the home page now does the work. If the token has no workspace, it waits for the user's row. Then it asks the provider for the user's memberships a few times, a couple of seconds apart. When one shows up, it refreshes the login for that workspace and sends the person home. If nothing shows up, it shows a card saying setup isn't done, with reload and sign-out buttons.

That means a slow webhook makes for a slow first page, sometimes tens of seconds.

Ask the source, but don't write

Earlier that day I'd removed an old setup page that used to do this waiting, and changed the shared lookup. Its retry waits now have a ceiling. When it runs out of retries, it asks the provider whether the user or workspace really exists, then checks my database once more. If the provider says yes and my database says no, it logs that the webhook isn't delivering.

It never creates the row itself. Two writers is how one person ends up with two workspaces.

The same commit fixed the welcome tour, which waited by counting screen frames. It now also watches for the element being added and gives up after a fixed number of seconds.

Where I think it's still broken

The refresh has to run where cookies can be written, and the page calls it in the middle of drawing the page. I'm not sure that's allowed.

My framework also redirects by throwing an error. The page wraps the refresh in a catch:

try { await refresh(tenant); } catch (e) { return stuckCard(); }

If that catch grabs the redirect too, a perfect signup ends on the card saying setup isn't done, telling someone their account failed when it didn't.

Keep reading

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

5 paths forward
Continue the thread

New users logged in to nothing because sign-up waited so long to pick a workspace

Workspace creation waited for a later webhook so invited people wouldn't get a spare one, and self-serve people could log in before it fired. Now the creation handler asks the identity provider for memberships on the spot, a later event repairs misses, and the session lookup retries, but the comment that justifies it contradicts the one it replaced and nothing tests which is right.

via #webhooks · #race-conditions · #reliability