fix(auth): revoke refresh token on logout + strip code/state from URL on callback #385

Merged
YousifShkara merged 1 commit from fix/MAPPS-336-spa-logout-revoke-url-strip into main 2026-06-30 06:02:24 +02:00
Owner

MAPPS-336: two surgical hardening items on the SPA OIDC surface.

Logout: the existing path cleared sessionStorage and location.replace(hub_logout) but never called revoke_refresh_token. The function has existed at src/modules/oidc/flow.rs:415 since the initial cutover; nothing in the SPA invoked it, so a rotated-but-not-revoked refresh family stayed usable at the OP until natural expiry (a stolen refresh outlived the user clicking "Log out"). Now the click reads the stored refresh token, fires revoke_refresh_token as a Dioxus spawn (RFC 7009 returns 200 for unknown tokens, so a network blip never blocks the local cleanup), then proceeds with the existing clear + navigate sequence.

Callback URL strip: the previous code relied on the Dioxus router's history.replaceState side-effect to scrub ?code=&state= after the token exchange. Racy in practice - if the router skipped or deferred the rewrite the code lingered in window.location, in browser history, and (more dangerously) in Referer headers on the next navigation. The exchange call is now followed by an explicit history.replace_state_with_url(NULL, "", Some("/auth/callback")) BEFORE the match arms run, so the sensitive query string is gone whether the exchange succeeded or failed.

No new dependency (uses the existing dioxus::prelude::spawn and web_sys::History already in the bundle), no schema change, no UX change.

#MAPPS-336

MAPPS-336: two surgical hardening items on the SPA OIDC surface. Logout: the existing path cleared `sessionStorage` and `location.replace(hub_logout)` but never called `revoke_refresh_token`. The function has existed at `src/modules/oidc/flow.rs:415` since the initial cutover; nothing in the SPA invoked it, so a rotated-but-not-revoked refresh family stayed usable at the OP until natural expiry (a stolen refresh outlived the user clicking "Log out"). Now the click reads the stored refresh token, fires `revoke_refresh_token` as a Dioxus `spawn` (RFC 7009 returns 200 for unknown tokens, so a network blip never blocks the local cleanup), then proceeds with the existing clear + navigate sequence. Callback URL strip: the previous code relied on the Dioxus router's `history.replaceState` side-effect to scrub `?code=&state=` after the token exchange. Racy in practice - if the router skipped or deferred the rewrite the code lingered in `window.location`, in browser history, and (more dangerously) in `Referer` headers on the next navigation. The exchange call is now followed by an explicit `history.replace_state_with_url(NULL, "", Some("/auth/callback"))` BEFORE the `match` arms run, so the sensitive query string is gone whether the exchange succeeded or failed. No new dependency (uses the existing `dioxus::prelude::spawn` and `web_sys::History` already in the bundle), no schema change, no UX change. #MAPPS-336
fix(auth): revoke refresh token on logout + strip code/state from URL on callback
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m38s
Create release / Create release from merged PR (pull_request) Has been skipped
464d60bca8
MAPPS-336: two surgical hardening items on the SPA OIDC surface.

Logout: the existing path cleared `sessionStorage` and `location.replace(hub_logout)` but never called `revoke_refresh_token`. The function has existed at `src/modules/oidc/flow.rs:415` since the initial cutover; nothing in the SPA invoked it, so a rotated-but-not-revoked refresh family stayed usable at the OP until natural expiry (a stolen refresh outlived the user clicking "Log out"). Now the click reads the stored refresh token, fires `revoke_refresh_token` as a Dioxus `spawn` (RFC 7009 returns 200 for unknown tokens, so a network blip never blocks the local cleanup), then proceeds with the existing clear + navigate sequence.

Callback URL strip: the previous code relied on the Dioxus router's `history.replaceState` side-effect to scrub `?code=&state=` after the token exchange. Racy in practice - if the router skipped or deferred the rewrite the code lingered in `window.location`, in browser history, and (more dangerously) in `Referer` headers on the next navigation. The exchange call is now followed by an explicit `history.replace_state_with_url(NULL, "", Some("/auth/callback"))` BEFORE the `match` arms run, so the sensitive query string is gone whether the exchange succeeded or failed.

No new dependency (uses the existing `dioxus::prelude::spawn` and `web_sys::History` already in the bundle), no schema change, no UX change.

#MAPPS-336
YousifShkara deleted branch fix/MAPPS-336-spa-logout-revoke-url-strip 2026-06-30 06:02:24 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!385
No description provided.