A second installed application declares the same private-use URI scheme as your native OAuth 2.0 client — where does the authorization response go?
answer
- the scheme is a claim, not a reservation
- no registry, no naming authority
- the platform decides, the specification cannot
- reverse domain name resists collisions
- a claimed https association removes the ambiguity
basics
~20 sWherever the operating system decides. A private-use URI scheme has no naming authority, so nothing stops a second application declaring the same string, and the platform's own resolution rule — not the specification — picks which one is handed the authorization response.
solid answer
~50 sThis is **scheme squatting**. The browser passes the redirect URI to the operating system, which looks up who declared that scheme; with two claimants the outcome is the platform's rule — first installed, last installed, or a prompt — and RFC 8252 cannot dictate it, because there is no registry for private-use schemes. The squatter then receives the query string, `code` and `state` included. What RFC 8252 asks for is a **collision-resistant** scheme: a domain name under the client's control, written in reverse order, which makes an accidental clash improbable and a deliberate squat obvious rather than plausible. It does not prevent one. Where the platform supports it, a claimed `https` redirect removes the ambiguity entirely because the association is verified against the domain. Separately, the authorization-code flow's proof key (RFC 7636) is what denies a captured `code` any value — that is a different mechanism from where the code lands.
code
pseudocode · 7 lines# two installed applications have declared the same private-use scheme
declared_by("com.example.parking") = [application A, application B]
on inbound "com.example.parking:/oauth2redirect/city?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj":
handler = operating_system.resolve("com.example.parking")
# the resolution rule belongs to the platform; no naming authority exists to appeal to
deliver(uri) to handlergo deeper
Know that a private-use scheme is only a name an application declares, and that a second application can declare the same one.
Explain who resolves the collision — the operating system, by its own rule — and what the reverse-domain convention does and does not buy you.
Show the production posture: prefer a claimed https redirect where the platform supports it, verify the scheme derives from a domain you control, and treat an unsolicited inbound redirect as something to discard.
The angle is that you cannot fix this at the protocol layer for every device you ship on, so you decide which platforms get the stronger form, what the fallback is worth, and how you would detect a squatter in the field.
## What squatting is A private-use URI scheme redirect works because the application told the platform, at install time, that it handles URIs beginning `com.example.parking:`. When the browser encounters such a URI it hands it to the operating system, which dispatches it to that application. The declaration is a **claim, not a reservation**. No registry exists for private-use schemes and no party verifies that a declarer has any relationship to the string. A second installed application may declare the identical scheme, and then two processes claim the same destination. Which one the operating system picks is a platform rule — some resolve to the first registrant, some to the most recent, some ask the user — and the specification has no authority to settle it. If the squatter wins the lookup, it receives the full redirect URI, including the `code` and the `state` the authorization server put in the query string. ## Why the response is readable at that point A common misreading is that the response is protected because the flow ran over HTTPS. The hop from the authorization server to the browser is indeed protected. But the final hop is not an HTTP request at all: it is the operating system handing a URI string to a local process. Transport security has nothing to act on there. ## What the specification actually asks for RFC 8252's mitigation is naming discipline: - Use a scheme **based on a domain name under the client's control, written in reverse order** — `com.example.parking`, not `parking` or `myapp`. - That makes an **accidental** collision — two independent developers picking the same short word — very unlikely. - It also makes a **deliberate** squat legible: an application declaring a scheme built from someone else's domain has no innocent explanation, which matters for review and for takedown, even though it does not stop installation. What it is not is enforcement. Nothing checks that the declarer owns the domain the scheme is derived from. Read the requirement for what it is: collision resistance, not authentication of the recipient. ## How the three forms compare on exactly this point | Form | Can another local process claim the destination? | What stands in the way | |---|---|---| | Private-use URI scheme | Yes — by declaring the same string | Naming discipline only | | Claimed `https` URI | No — the association is verified against the domain | The platform's domain check | | Loopback address | Yes — by binding the port first | Being first to hold an ephemeral port | So the honest answer to "how do I stop squatting?" is *change form where the platform allows it*. A claimed `https` redirect is the one whose destination another installed application cannot simply assert. ## What this question is not about Two boundaries are worth keeping clear when answering. 1. **What makes a captured code worthless** is the authorization-code flow's per-request proof key (RFC 7636), and it is a separate mechanism. It is the reason interception is survivable; it is not the reason the redirect is well addressed. A strong answer names both halves and does not collapse them into one. 2. **Where the token ends up afterwards** — how the application stores what it received — is a different subject again, and not part of the redirect's design. ## The review posture When you inspect a native client, three things are checkable in a minute: the scheme is derived from a domain the team actually controls; a claimed `https` redirect is registered too wherever the platform supports one; and the application does not treat an inbound redirect as trustworthy simply because it arrived — a response for a flow this process never started should be discarded, not exchanged.
- Does registering the redirect URI with the authorization server prevent this?No. Registration governs where the authorization server is willing to send a response; squatting happens after the response has already been sent to exactly that URI. The two controls act at different ends of the flow, and neither substitutes for the other.
- What can the client itself do when a redirect arrives?Treat an inbound authorization response as unsolicited until it matches a flow this process actually started — check the `state` it generated for that request and discard anything that does not correspond. An application that exchanges whatever arrives will act on a response it never asked for.
- If a claimed https redirect is stronger, why does the private-use scheme still exist?Because the claimed form depends on the platform's domain-association mechanism being available and correctly configured, and on the application being installed. The private-use scheme is the fallback that works without any of that, which is why clients commonly register both and prefer the claimed URI where it functions.
Anyone may paint any number on their front door. Writing your street name into the number makes a duplicate unlikely and an impostor obvious — but the postal worker still has to hand the envelope to one of the two doors.
saying these in an interview costs you the question
- Says the operating system guarantees a private-use scheme is unique.
- Believes registering the redirect URI with the server prevents squatting.
- Thinks a reverse-domain scheme is enforced by the domain's owner.
- Assumes only a compromised device can declare a rival scheme.
- Claims a squatter cannot read the code because the response travelled over TLS.