Skip to content

feat: let desktop apps and CLIs sign in with a loopback redirect - #589

Closed
RamonGebben wants to merge 1 commit into
dev-null-sh:productionfrom
RamonGebben:feat/loopback-redirect-uri
Closed

RamonGebben wants to merge 1 commit into
dev-null-sh:productionfrom
RamonGebben:feat/loopback-redirect-uri

Conversation

@RamonGebben

Copy link
Copy Markdown

Why

Signing in a device (/mobile/authorize → POST /api/v1/devices) only redirected to devnull://, the iOS app's URL scheme. So phones were the only devices that could sign in. A TUI, another CLI or a desktop app can't register a custom URL scheme reliably, but it can listen on a local port. RFC 8252 (OAuth for native apps) recommends exactly that.

This PR lets any native app on the user's machine sign in as a device, next to the phone app.

What changes

  • Loopback redirects. redirect_uri may now be http://127.0.0.1:<port>/… or http://[::1]:<port>/…, besides the devnull:// prefix.
  • Parsed, not prefix-matched. Just adding http://127.0.0.1: to redirect_uri_prefixes would be unsafe, because starts_with accepts http://127.0.0.1:80@evil.example/callback, whose real host is evil.example. The new App\Rules\DeviceRedirectUri parses the URL instead. It requires scheme http, host exactly 127.0.0.1 or [::1], a port, and no user, password or fragment. localhost is rejected, as RFC 8252 prefers the literal IP.
  • Easy to extend. Any new app with its own URL scheme only needs a line in config('mobile.redirect_uri_prefixes'). The rule is device-neutral, and both the authorize page and the Approve POST use it through parameterRules().
  • Same guarantees. The code is still bound to the key it was issued for and redeemable only with the PKCE verifier, so another local process that catches the redirect can't use it.
  • Docs. README → Signing in describes the loopback option for desktop apps and CLIs.

Out of scope: the /mobile/... route and *MobileAuthorization* class names still say "mobile". Renaming them is a bigger, separate change.

Tests

  • Accepted: http://127.0.0.1:53682/callback, http://[::1]:53682/callback.
  • Rejected: http://127.0.0.1:80@evil.example/x, http://localhost:1234/x, https://127.0.0.1:1/x, http://127.0.0.1.evil.example/x, a loopback URL without a port, and a loopback URL with a fragment.
  • Approving with a loopback redirect_uri sends a 303 to it with code and state.
  • composer test:lint, test:types and test:type-coverage pass. Pest passes with CI's flags (2991 tests, 100% coverage), and so does bun run test:unit.

The device authorize flow only redirected to devnull://, so only the phone
app could sign in. A desktop app, a TUI or another CLI on the user's machine
can now use a loopback redirect_uri (http://127.0.0.1:<port>/... or
http://[::1]:<port>/...), as RFC 8252 recommends for native apps.

The loopback address is parsed instead of prefix-matched, so
http://127.0.0.1:80@evil.example can't send a code to another host. Other
URL schemes for future apps stay a one-line config change. Either way the
code only works with the key it was issued for and the PKCE verifier.
@RamonGebben

Copy link
Copy Markdown
Author

Closing because issue would be solved by #585 in a way better way. Making this obsolete.

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