Skip to content

Check the whole OAuth redirect and revoke tokens on sign-out - #43

Merged
TeoVader merged 3 commits into
mainfrom
fix/oauth-callback-strict-and-revoke
Nov 11, 2026
Merged

TeoVader merged 3 commits into
mainfrom
fix/oauth-callback-strict-and-revoke

Conversation

@TeoVader

Copy link
Copy Markdown
Owner

What changes

Before:

  • The app took any intent or loopback request as the OAuth redirect as long as its scheme matched the registered redirect URI (AuthManager.isRedirect). The state check stopped a redirect the app never started, but the same attempt (state + PKCE verifier) stayed valid until the next sign-in, with no age limit.
  • Signing out only deleted the tokens on the phone; the refresh token stayed valid at Adobe.

After:

  • A redirect counts only when its scheme, host, port and path all match the registered redirect URI (for loopback redirects, localhost and 127.0.0.1 are the same host, since the listener only ever reports 127.0.0.1), and it must carry the state of the pending sign-in. The redirect carrying that state uses the attempt up straight away, before the code exchange, so a replayed or duplicate redirect finds nothing. An attempt older than 10 minutes is refused with "Sign-in expired, please retry." The code is exchanged with the redirect URI the attempt started with.
  • Signing out also revokes the refresh and access tokens at Adobe IMS. This runs in the background with a 10 s deadline and is best effort: the user is signed out at once whatever happens, and errors or no network are ignored. A sign-out because Adobe already rejected the refresh token revokes nothing.

How

  • New core/auth/OAuthCallback.kt:
    • matches parses the URIs with java.net.URI, so it is pure JVM and unit-tested;
    • check decides code or failure for a callback against a PendingSignIn;
    • revokeTokens posts client_id and token to /ims/revoke.
  • Revocation follows Adobe's IMS reference (developer.adobe.com/developer-console/docs/guides/authentication/UserAuthentication/ims, "Token revocation"): POST https://ims-na1.adobelogin.com/ims/revoke. client_id is required for "OAuth Native App" and the other public credentials, and only Web credentials send Basic auth, so there is no secret. Both parameters go in the form body, which the docs recommend for sensitive data. The docs don't say whether revoking the refresh token also kills access tokens, so both are revoked.
  • AuthManager stores the attempt's redirect URI and start time next to the verifier and state (under a lock), takes the pending attempt atomically in completeRedirect, and gets the app scope for the background revocation.
  • An error redirect (e.g. the user cancels) now needs the right state too; without it the message is "OAuth state mismatch — please retry." instead of Adobe's text.

Checks

  • gradlew.bat :app:testDebugUnitTest :app:verifyRoborazziDebug :app:lintDebug on the workstation: 248 tests, only the 2 known Windows-only screenshot differences (notifications, tabletNotifications); lint "No issues found". OAuthCallbackTest has 18 new tests: scheme, host, port and path mismatches, default ports, loopback host equivalence, state, expiry, Adobe errors, the revocation request (client id, no Authorization header, tokens de-duplicated) and revocation that never throws.
  • New or changed UI has a Roborazzi screenshot (no UI change)
  • Tried on a phone: not done. Sign-in (custom scheme and loopback) and sign-out should be tried on a phone before release; I made no real calls to Adobe.

🤖 Generated with Claude Code

TeoVader and others added 3 commits November 11, 2026 16:55
The callback was accepted when only its scheme matched the registered
redirect. It now must match scheme, host, port and path (loopback
localhost and 127.0.0.1 count as one host), carry the state of the
pending attempt, and arrive within 10 minutes. The redirect that
carries the attempt's state consumes it, so a replayed or duplicate
callback finds nothing; the code is exchanged with the redirect URI the
attempt started with.

A user's sign-out now also revokes the refresh and access tokens at
Adobe IMS (POST /ims/revoke with the client id, as documented for
public Native App credentials), in the background with a 10 s deadline.
Failures are ignored and never hold sign-out up; a sign-out after Adobe
rejected the refresh token revokes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TeoVader
TeoVader merged commit c83fbf6 into main Nov 11, 2026
2 checks passed
@TeoVader
TeoVader deleted the fix/oauth-callback-strict-and-revoke branch November 11, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant