DEV Community

John Builds
John Builds

Posted on

iOS Universal Links follow redirects, and that quietly breaks OAuth

I lost a day to an OAuth bug that produced no errors, no logs, and no failed requests. On an iPhone with the provider's app installed, tapping "Connect" opened that app to its feed. No authorization dialog, no callback, nothing on the server. The flow just evaporated.

My first theory was that the authorize URL was registered as a Universal Link, so iOS was routing the whole thing to the app. I shipped a parameter to force the web dialog. It changed nothing, because the parameter does not exist. I had picked it up from a URL I saw in the wild and assumed it did what its name suggested.

The actual mechanism:

The provider's apple-app-site-association file explicitly excludes /oauth/authorize from Universal Links. They want the dialog in a browser. But if you have no web session with them, and almost nobody who lives in the app does, the authorize URL responds with a 302 to /accounts/login. That path is not excluded. It matches the catch-all.

And iOS evaluates Universal Links across the server redirects of a tap-initiated navigation, not just the URL you started with. So the excluded path handed off to a registered one, and the app swallowed the flow.

The fix is not a provider parameter. Universal Links fire on tap-initiated navigations and not on programmatic ones. Open a blank popup synchronously inside the click handler, so the popup blocker stays happy, then navigate it with location.assign. The whole redirect chain stays in the browser.

Two things I would do differently. Check the provider's AASA file before theorizing about deep links; it is a public URL and it disproved my first theory for free. And follow the entire logged-out redirect chain with curl, not just the entry URL. An excluded path that redirects to a registered one still deep-links.

I was certain the auth URL was broken. It was the login page it redirected to.

Top comments (0)