3 min readAUTHENTICATION · ACCESS-CONTROL · METHODOLOGY

No step-up is an account-takeover primitive

A password or email change that accepts only a bearer token, with no current password and no fresh-auth check, is an ATO primitive on its own. Here is how I test for it and why "you need the token" is a weak defence.

SHOULD STEP UP current password MFA fresh auth (auth_time) ABSENT ABSENT ABSENT POST /password BEARER ONLY account takeover FULL CONTROL
A password change with none of the checks that should gate it

Most people file “change password has no current-password field” as informational. That is the wrong instinct. The missing re-check is not a hardening nit, it is the last gate between a leaked or borrowed token and a permanent takeover. Treat it as a primitive and the severity argument writes itself.

The shape of the request

Here is the endpoint that should make you stop. A change-email handler, sanitised:

http
POST /api/v1/account/email HTTP/1.1
Host: app.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Content-Type: application/json

{"new_email":"attacker@example.com"}

Notice what is not in that body. No current_password. No recently issued re-auth token. No challenge. The server reads the subject from the JWT, updates the record, and (if you are unlucky) does not even send a confirmation to the old address. Whoever holds a valid bearer at request time owns the account from that point on, because the recovery address is now theirs.

Password change is the same story:

http
POST /api/v1/account/password HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

{"new_password":"attacker-controlled"}

If that succeeds without proving the current password or a fresh login, every other session is irrelevant, the attacker just sets a credential they know.

Why “but you need the token” is a weak mitigation

The reflexive defence from engineers is that you already have to be the user to make the request. That argument leaks for three concrete reasons, and I name all three in a report so triage cannot wave it away.

The through-line: a bearer proves you made a request, it does not prove you are present and intentional right now. Sensitive actions need the second thing.

What a correct gate looks like

Three mechanisms, in rough order of strength, and they stack:

  • current_password on the change-password and change-email handlers. Cheap, and it defeats a stolen token outright because the attacker still does not know the existing credential.
  • Auth-time freshness. OIDC carries auth_time in the ID token. The server re-checks it on sensitive actions and rejects anything older than a few minutes, forcing a re-login. A refresh token alone cannot bump auth_time, that is the whole point of the claim.
  • Step-up / re-authentication. Push the user through MFA or a fresh password prompt immediately before the action, scoped to that action.

If the email is changing, the old address gets a notification and ideally a reversal link, so a takeover is at least loud and recoverable.

How I test it, fast

  1. Log in, capture a sensitive-action request (password, email, MFA disable, recovery-phone change).
  2. Strip every field that is not the new value. Resend. If it still succeeds, the current-password gate is absent.
  3. Let the access token age, or use a token minted purely from a refresh token, so auth_time is stale. Resend. If it succeeds, there is no freshness check.
  4. Confirm the old email or phone receives no notification. Silent change is the difference between a high and a critical.

Two minutes, four requests. The finding is not “no current-password field,” it is “a valid token, by any of the routes a token escapes, is sufficient for permanent account takeover, and nothing makes that loud.” Frame it as the primitive it is and the severity follows on its own.