Back to Feed
article 3m read

Refresh token discarded by the connect callback, on purpose

One issue tracker started sending refresh tokens with short-lived access tokens, and my connect step had been written to discard them. I now save the refresh credential everywhere, renew based on what is actually stored, and retry once, with a few hazards still open.

Article DepthPractice

Flow

How each saved shape gets renewed

  1. 1Access and refresh saved

    Refresh, then save both new tokens. A rejected refresh means reconnect.

  2. 2Only an old long-lived token

    Trade it in for a short-lived token plus a refresh token, and save both.

  3. 3Code host token

    No refresh token. Make a new one on a 401; pass other errors along.

  4. 4Nothing saved

    Ask for a reconnect.

  5. 5Reconnecting an old connection

    Now adds the missing refresh token instead of keeping the gap.

Discarding the refresh token was something the issue-tracker connect step did on purpose. It kept the access token, saved nothing for the refresh token, and had a comment saying this provider never sends one.

That used to be true. Newer apps on that provider now get short-lived access tokens plus a refresh token. Older apps get long-lived tokens and no refresh token. So a new connection to my metrics app might work for years, or it might work until its first expiry and then need a full reconnect. I haven't checked whether a real connection ever hit this. The code was enough.

Three things I was calling "the token"

Each integration renews a different way.

The second tracker gives a new access token and a new refresh token on every refresh. Save both, because the old refresh token is used up. If the refresh is rejected, ask for a reconnect.

The first tracker might hold either kind, so refresh now checks what's stored.

If there's a refresh token, use it. If there's only the old token, trade it in. If there's neither, ask the user to reconnect.

The middle line uses the provider's trade-in: send the old long-lived token, get a short-lived one plus a refresh token, and save both. My first version saved the new secret under the wrong organization ID, and a later commit that day fixed it.

The code host has no refresh token at all. The app makes a new token from its own credentials when the old one gets a 401. Any other error is passed along, not treated as an expired token.

Connect stopped dropping it

Connect now saves the refresh token on every path, including reconnects, and a reconnect can add a missing one to an old connection. It logs whether a refresh token showed up, but never the token itself.

When a call fails on auth, the app refreshes once and tries once more. If that fails, it says to reconnect.

Weak spots I can see

I'm not sure which kind of app mine is. The new refresh tests only cover the code host. The first tracker spots auth errors by matching words in the error message, which breaks if the library rewords them.

The trade-in does three saves in a row. If it crashes after the first, the connection is left with a short-lived token and no refresh token, which is the same hole I started with.

Keep reading

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

5 paths forward