🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)
🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)

🔧 Programmierung 🕛 kürzlich 5 Min Lesezeit
0

My app's anti-theft feature locked every user out

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

A real incident from the finance app I build solo. The security design

was textbook. That's exactly why it bit me.





The textbook setup



Short-lived access token, long-lived rotating refresh token. Every

time the client refreshes:




  1. It presents its refresh token.

  2. The server verifies it, revokes it, and issues a brand-new
    access + refresh pair.

  3. The old refresh token is now dead — single use.



And the part everyone recommends (OWASP calls it refresh-token reuse

detection): if a client ever presents a refresh token that's already

been revoked
, treat it as theft. An attacker who stole a token and

replayed it would trip this. The safe response is scorched earth —

revoke every session for that user so both the attacker and the

victim get kicked and must sign in fresh.



This is correct. It catches a real attack. Ship it.





The incident



One evening my own app showed me a auth error over a €0.00 balance and

an "add your first account" screen — as if I'd never signed in. The

data was all there on the backend. Only the login was dead.



The audit log told the story. All day: healthy refreshes every ~15

minutes. Then, one refresh, the server saw an already-revoked token,

declared theft, and revoked all sessions. The "attacker"?



Every session in the chain carried the same device id. It was my

phone. My phone was the thief.





How a device robs itself



A same-device replay of its own just-rotated token has completely

benign causes, and they're all common:





  • Lost response. The server rotated the token and committed, but the
    HTTP response never made it back (dropped connection, backgrounded
    app, dead tunnel). The client still holds the old token and retries
    it. To the server: a revoked token, replayed.


  • A failed local write. The new token pair came back, but writing it
    to the keychain failed (or raced with app suspension). Next launch,
    the client presents the previous token.


  • Two processes racing. My app and its widget extension both hit a
    401 and both refresh with the same token. One wins and rotates it; the
    other arrives a beat later with what is now a revoked token.



None of these are theft. But "revoked token replayed" looked identical

to theft, so the nuke fired and I logged myself out everywhere.





The insight



Here's the thing I missed: for a same-device replay, the revoke-all

adds almost no security.



Reuse-detection defends against an attacker who exfiltrated a refresh

token and replays it from somewhere else. That "somewhere else" is

the signal. An attacker replaying from the same device, with the same

bound device id, already had full access to the device's storage — the

nuke doesn't save you there; they could just present the live token.



So the theft signal isn't "a revoked token was replayed." It's "a

revoked token was replayed from a different device than the one the

session is bound to
."





The fix



Bind sessions to a device id, then scope the scorched-earth response to

the cases that are actually suspicious:




CODE
if (session.revokedAt) {
const sameDeviceBenignReplay =
session.deviceId !== UNBOUND &&
session.deviceId === presentedDeviceId &&
session.revokedReason === "rotated";

if (!sameDeviceBenignReplay) {
await revokeAllSessions(session.userId, "theft_detected");
}
throw new RefreshError("token_replayed"); // 401 either way
}








  • Same device, replaying its own rotated token → plain 401. The
    client just re-authenticates its refresh normally or picks up the
    winner's new token. No nuke.


  • Different device, or a non-rotation revocation (sign-out), or an
    unbound session we can't attribute → keep the full revoke-all.
    Real
    theft signals still get scorched earth.



Three supporting fixes made it robust:





  1. A cross-process refresh coordinator. The app and widget now go
    through a single-flight mutex (a file lock in a shared container +
    a notification), so only one refresh is ever in flight per device.
    The loser waits for the winner's token instead of racing it.


  2. Harden the token write. Check the keychain write status, retry
    once, and log loudly on failure — a silently failed write was one
    of the replay causes.


  3. Never turn a transient failure into a logout. A dropped network
    or a 5xx on the refresh endpoint must be retryable, not "your
    session is dead." Only a genuine 401/403 from the auth server means
    sign out.






Takeaways




  • Rotating refresh tokens + reuse detection is still the right design.
    Keep it.

  • But "reused token" is not the same as "stolen token." The stolen
    signal is reuse from an unexpected device. Bind sessions to a
    device and let that distinction gate your most destructive response.

  • Make client refresh single-flight across every process that shares
    the credentials, or your own app becomes the attacker.

  • Distinguish transient failures from auth rejections everywhere in the
    refresh path. Logging users out on a network blip is its own outage.



The uncomfortable lesson: a security control that's too eager is

indistinguishable from a bug to the person it locks out. Scope the

blast radius to the actual threat.






I'm building EveryPenny, a private,

multi-currency money tracker for iPhone. This one shipped a fix the

same day — solo means the postmortem and the patch are the same

afternoon. Ask me anything about the auth model in the comments.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Hackers Just Poisoned the Rust Supply Chain | Threat Wire
1 Quelle
Hackers Found a Way Into Humanoid Robots | Threat Wire
1 Quelle
Bits und so #1021 (Passwort für Laufwerk)
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten My app's anti-theft feature locked every user out

Thematisch verwandte Begriffe: apps, antitheft, feature, locked · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...