API keys and tokens: the credentials you forget you issued

Practical guides · About 7 minutes

You changed the password, enabled 2FA, and removed the departing colleague's account. The integration they set up eighteen months ago is still running, still authenticated, and still has write access. Long-lived tokens are the credentials that don't show up in a user list and don't care what you did to the password — which is exactly why they're worth a guide of their own.

Why tokens behave differently

A password is checked at login and produces a session that expires. A token is the session, and usually one without an expiry date. That difference produces several properties people find surprising in the moment:

Finding what you've already issued

Start by assuming you have more than you think. Places to look, roughly in order of how much trouble each can cause:

Per-user tokens. Most platforms have a "personal access tokens", "API keys", or "developer settings" page under the user profile. These carry the permissions of the person who made them, which is often more than the integration needed.

Connected applications and OAuth grants. A separate list, usually under "authorised apps", "connected accounts", or "third-party access". These are the ones you clicked through years ago — the analytics tool, the scheduler, the browser extension that wanted access to your mail. Anything unrecognised gets revoked.

Service accounts and machine users. Accounts created for automation rather than a person. They're easy to overlook precisely because they don't correspond to anyone, and they're often the most privileged things you own.

Webhooks. Not credentials exactly, but they push your data somewhere. A webhook aimed at a URL nobody recognises is worth investigating and then deleting.

Deploy keys and SSH keys. On code hosting, these grant repository access independent of any user account.

App-specific passwords. Where a provider issues a separate password so a legacy client can bypass 2FA. Every one is a permanent, single-factor way into the account.

The rule for unidentifiable tokens

If you can't say what a token is for, revoke it. Either nothing breaks — in which case it shouldn't have existed — or something breaks loudly and you've just discovered an undocumented dependency, which you needed to know about anyway. Do this on a Tuesday morning, not a Friday afternoon.

Issuing them properly

Every new token is a small decision that compounds. Six habits keep it manageable:

Name it for its job. ci-deploy-staging, not test or new key. The name is the only context the next person gets, and there will be a next person even if that's you in a year.

Scope it down. Read-only unless it genuinely writes. One service's data rather than all of it. If a platform supports resource-level or repository-level scoping, use it — this is the single most effective control here, because it caps the damage of a leak rather than trying to prevent one.

Set an expiry. Where the platform allows it, always. A token that expires is a token you're forced to review. Renewal friction is the point.

Prefer a machine identity over a person's token. A token tied to an individual dies with their account, breaking production at the worst time, and carries their full permissions. A dedicated service account can be scoped narrowly and survives staff changes.

Restrict by network where you can. An IP allowlist on a token used by fixed infrastructure means a leaked token is useless from anywhere else. Rarely available, extremely effective when it is.

Record it. One line in your inventory: what it is, what it can do, what depends on it, when it expires. This is what makes the quarterly review possible instead of archaeological.

Where they should live

Tokens are credentials and belong in the same place as your other credentials, with one addition: whatever system runs the automation needs a secret store.

If your code host offers automatic secret scanning, turn it on. It catches the accidental commit before someone else does.

Rotating without breaking production

The reason token rotation gets avoided is the fear of taking something down. The technique that removes that fear is straightforward, and it's worth knowing before you need it in a hurry:

  1. Create the new token first, scoped to what the job actually needs — not necessarily what the old one had.
  2. Deploy the new token everywhere it's used. Both tokens are now valid, so nothing is down.
  3. Verify the new one is in use. Many platforms show a "last used" timestamp on each token — the old one going quiet is your confirmation.
  4. Wait through one full cycle of whatever uses it. A monthly batch job means waiting a month before step 5.
  5. Revoke the old token.
  6. Watch for failures for a day. If something breaks, you know exactly what caused it.

The one case where you skip the overlap is a confirmed leak. Then revoke first and repair afterwards — a broken integration is a smaller problem than an active token in someone else's hands.

When a token leaks

Order matters here, and the instinct to investigate first is the wrong one:

  1. Revoke it. Immediately. Before understanding what happened, before telling anyone. Every minute it stays valid is a minute of exposure, and you can investigate a revoked token just as well.
  2. Issue a replacement and restore the service.
  3. Check what it did. Audit logs on the affected service, filtered to that token or service account. You're looking for activity you can't attribute.
  4. Look for what it created. A token with sufficient permissions can mint other tokens, add users, or set up forwarding. Revoking the original doesn't undo any of that.
  5. Purge the exposure. If it was committed, remember that removing the line doesn't remove it from history. If it was in a chat or ticket, delete the message and check exports.
  6. Fix the cause. A token in a repository usually means the workflow made that the easy option. Secret scanning and a proper secret store fix the class of problem; deleting one commit fixes one instance.

Add tokens to your offboarding list

Worth stating separately because it's the step most often missed. When someone leaves, disabling their account may leave every token they created fully functional. Go through each Tier 1 and Tier 2 service and revoke tokens they issued — and if any of those tokens are load-bearing, that's a signal to replace them with a service account rather than simply reissuing under someone else's name.

The same applies when an integration is retired. Removing the tool from your stack should include revoking its access; a decommissioned service with a live token pointed at your data is a strange thing to leave running.

Related guides