- RFC 8252: OAuth 2.0 for Native AppsDenniss & Bradley (IETF, October 2017) - BCP 212; sections 4, 6, 7 and 8 carry everything above
- RFC 7636: Proof Key for Code Exchangethe companion extension; section 1 describes the interception attack 8252's redirect options are designed around
- RFC 9700: Best Current Practice for OAuth 2.0 SecurityLodderstedt et al. (IETF, January 2025) - extends the native-app floor to all public clients
- RFC 10017: OAuth 2.0 for Browser-Based ApplicationsParecki, De Ryck, Waite (IETF, August 2026) - the SPAs' equivalent BCP
Published October 2017 (Denniss & Bradley), RFC 8252 is a best current practice, not a new grant: it takes the authorization-code grant you already know and answers the question a phone app raises immediately - where does the redirect go? A web app receives the authorization code on its own server. A native app has no server. The answer shapes everything else in the document: the code has to land back on the same device, delivered through the operating system's URI dispatch, and redeemed by the app itself over the back channel.
It pairs with the 2015 reality that PKCE (RFC 7636) was written for: apps receive redirects through mechanisms where another app can intercept them. That is why the two documents travel together - 8252 without PKCE is the code-interception attack with the serial numbers filed off.
Section 4 is blunt about the architecture: the authorization request is made "in an external user-agent (typically the browser) rather than an embedded user-agent (such as one implemented with web-views)". The app hands the request to the browser; the browser does the login and consent; the OS hands the response back to the app. Only steps 1 and 4 below touch anything the app does not fully control - which is exactly why the response needs to be tamper-checked (PKCE, step 6).
Single sign-on is a side effect of using the real browser: it already holds the user's session at the authorization server, so a second app's login is often one tap, not a password entry (section 4).
// step 4, landed: the OS dispatches the redirect to the app's local listener
GET /callback?code=<authorization-code>&state=<state> HTTP/1.1
Host: 127.0.0.1:{port}The front-channel leg ends at whatever ephemeral port the OS handed the app; the port may vary per request - the one sanctioned exception to exact redirect matching (section 7.3). Then the back-channel leg, where PKCE earns its keep:
// step 5: the app redeems the code itself, over TLS on the back channel POST /token HTTP/1.1 Host: as.example.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=<authorization-code> &redirect_uri=http%3A%2F%2F127.0.0.1%3A%7Bport%7D%2Fcallback &client_id=<client-id> &code_verifier=<pkce-verifier>
The redirect_uri repeats the loopback origin, and thecode_verifier is what an intercepting app cannot supply - the code-interception attack of section 8.1 dies here.
Section 7 defines three ways for the response to land back on the device, and they are not equal. What varies is who can register to receive the redirect - which is exactly what an attacker needs.
| Option | Shape | Who can receive it | Watch out for |
|---|---|---|---|
| Private-use URI scheme section 7.1 | com.example.app:/oauth2redirect | Any app that registers the same scheme - and multiple apps typically can | The classic interception vector: registration is usually first-come, not exclusive |
| Loopback interface section 7.3 | http://127.0.0.1:{port} or http://[::1]:{port} | Whatever is listening on that loopback port | Other local apps may reach the same loopback interface on some OSes |
| Claimed "https" scheme section 7.2 | https://app.example.com/callback | The app that provably owns the domain (app links / universal links) | Still a public client; URI travels through the OS dispatcher with unknown security properties |
The loopback option carries the document's one famous MUST: "the authorization server MUST allow any port to be specified at the time of the request" (section 7.3). The app binds whatever ephemeral port the OS hands it, so the registered URI is http://127.0.0.1:{port} with the port treated as variable. This is the one sanctioned exception to exact-string redirect matching in the entire ecosystem - RFC 9700 §2.1 keeps it, and only it, when it makes exact matching universal.
Before 8252, apps embedded the login page in a webview. Section 4 and 8.12 bury that practice, for three reasons. First, the host app can see everything in an embedded agent - credentials, cookies, everything - so the user is trusting every app they log into with their password. Second, no shared session: every app's webview starts cold, so no single sign-on. Third, the user cannot tell who is asking: a fake login page inside a webview is indistinguishable from the real one, and phishing gets a permanent home.
The middle ground many platforms shipped - in-app browser tabs (Custom Tabs on Android, ASWebAuthenticationSession on iOS) - counts as external: the page renders in the real browser process with its cookies and security model, while the UX stays inside the app (section 6). Modern "Sign in with Apple/Google" buttons in mobile apps are exactly this.
| Rule | Where | What it means |
|---|---|---|
| PKCE is not optional | section 6 | "Public native app clients MUST implement PKCE, and authorization servers MUST support PKCE for such clients." Servers SHOULD reject requests from native apps without it (section 8.1, citing RFC 7636 section 4.4.1's invalid_request). |
| No client secrets in the binary | section 8.5 | A secret compiled into an app distributed to millions "should not be treated as confidential" - any user can extract their copy. Servers that still demand one MUST treat the client as public and not accept the secret as identity proof. |
| Implicit stays out | section 8.2 | The implicit grant "generally works" with URI-based redirection, the document notes - then warns it should not be used, because the access token itself crosses the front channel. Native apps get the code grant with PKCE, not implicit. |
| Distrust the dispatcher | sections 8.7-8.9 | Fake user-agents, malicious ones, and cross-app request forgery are all in scope: the response URI is attacker-reachable by design, which is the whole reason state checks and PKCE exist on this platform. |
Where it stands today: RFC 9700 (January 2025) extends the same floor to every public client, not just native apps, and the browser-based apps BCP (RFC 10017, August 2026) does the same for SPAs. RFC 8252 is where the ideas debuted.
The code-interception attack PKCE was built for is the custom-scheme hijack described in section 8.1. You can run the full defense in the PKCE console - tamper buttons included.