Elevating OAuth2.0 for Security of Native Clients
Alina Boshchenko
Hacktivity 2023 - IT Security Festival · Day 1 · Theater
Overview
In this insightful Hacktivity conference talk, Alina Boshchenko delves into the critical security challenges of OAuth 2.0 when applied to native client applications. The presentation focuses on Proof Key for Code Exchange (PKCE), often pronounced "Pixie," an essential extension designed to fortify the authorization code flow against interception attacks. Boshchenko meticulously unpacks the underlying vulnerabilities inherent in native client authentication, where traditional client secrets cannot be reliably protected, and demonstrates how PKCE provides a robust, yet not entirely infallible, layer of defense.

Key moments
- 0:00 Introduction to OAuth 2.0 and Pixie
- 3:58 Why authorization code flow needs protection
- 8:00 Understanding the authorization code interception attack
- 10:00 How Pixie's proof of possession works
- 12:30 Pixie bypass via app impersonation attack
- 16:00 Mitigating Pixie bypass and deep linking
- 17:30 Best practices for secure native client OAuth
- 19:45 Conclusion: Pixie's impact and limitations
Elevating OAuth2.0 for Security of Native Clients
Speakers: Alina Boshchenko
Conference: Hacktivity
YouTube: https://www.youtube.com/watch?v=lV8WCnVY64I
Overview
In this insightful Hacktivity conference talk, Alina Boshchenko delves into the critical security challenges of OAuth 2.0 when applied to native client applications. The presentation focuses on Proof Key for Code Exchange (PKCE), often pronounced "Pixie," an essential extension designed to fortify the authorization code flow against interception attacks. Boshchenko meticulously unpacks the underlying vulnerabilities inherent in native client authentication, where traditional client secrets cannot be reliably protected, and demonstrates how PKCE provides a robust, yet not entirely infallible, layer of defense.
The talk is particularly relevant for developers, security architects, and anyone involved in building or securing modern mobile and desktop applications that rely on OAuth 2.0 for federated authentication. It highlights the evolution of OAuth 2.0 security, from its foundational principles to the necessity of extensions like PKCE in mitigating sophisticated attack vectors. By explaining both the problem and the solution in detail, Boshchenko equips the audience with a deeper understanding of secure OAuth 2.0 implementation for public clients.
Boshchenko’s presentation underscores the continuous cat-and-mouse game in cybersecurity, where even well-established protocols require constant refinement. PKCE, now a widely adopted standard, is presented not as a silver bullet, but as a crucial component of a multi-layered security strategy. The discussion extends to potential bypass techniques and comprehensive defensive implications, emphasizing that robust security demands both technical implementation and user awareness.
Background
▶ Watch: Introduction to OAuth 2.0 and Pixie (0:00)
OAuth 2.0 has emerged as the de facto standard for federated authentication and authorization, enabling a resource owner (typically a user) to grant a client application limited access to resources hosted by a resource server, without sharing their credentials directly with the client. Unlike more enterprise-specific protocols such as SAML, OAuth 2.0 offers a generic, flexible framework suitable for a wide array of applications, including native mobile and desktop clients. The core principle revolves around establishing a trusted relationship between the resource owner, the client application, an authorization server, and a resource server.
A cornerstone of OAuth 2.0, particularly for client applications, is the Authorization Code Flow. This flow is designed to mitigate the risks associated with exposing sensitive tokens directly to the browser or client-side application. The process typically involves several steps:
- The user initiates an action in the client application requiring access to protected resources.
- The client application redirects the user's browser to the authorization server, requesting authorization.
- The authorization server authenticates the user and presents a consent form, asking the user to grant specific permissions to the client application.
- Upon user consent, the authorization server issues a one-time authorization code and redirects the user's browser back to the client application's registered redirect URI.
- The client application, from its backend, exchanges this authorization code for an access token (and often a refresh token) by making a direct, back-end-to-back-end request to the authorization server's token endpoint.
- The access token is then used by the client application to access protected resources on the resource server.
The inherent complexity of this flow, particularly the exchange of an authorization code for an access token, is deliberate. Directly issuing an access token via HTTP redirects (steps 2-4) would expose it in browser URLs or headers, making it vulnerable to logging by proxies or other intermediaries. The authorization code, with its even shorter lifespan than an access token (which itself typically lasts only half an hour to an an hour), significantly reduces the attack surface. The crucial back-end-to-back-end exchange at the token endpoint ensures that the valuable access token is never exposed in the browser or via redirect, provided all communication occurs over a secure channel like TLS.
A critical aspect of the redirect mechanism is how the authorization server determines where to send the authorization code back. To prevent an attacker from constructing a malicious URL to intercept codes, client applications must register a predefined list of allowed redirect URIs with the authorization server. For native applications, this often involves leveraging operating system features that allow apps to claim specific URL patterns. On Android, this is done via intent filters in the Manifest file, while iOS uses Universal Links. An alternative involves generating custom URL schemes (e.g., my-app://callback), or using reverse domain notation (e.g., com.example.app). The challenge with custom schemes is that multiple applications can register for the same scheme, making it difficult for the operating system to determine which legitimate application should receive the authorization code. This potential for scheme collision creates a significant vulnerability: the Authorization Code Interception Attack.
This vulnerability is exacerbated for public clients, such as mobile apps or single-page applications (SPAs). Unlike confidential clients (e.g., web server applications) that can securely store a client secret for authentication at the token endpoint, public clients cannot guarantee the confidentiality of any embedded secret. This means that if an attacker manages to intercept an authorization code from a public client, they could potentially exchange it for an access token without needing a client secret, as the client ID is often publicly known. This fundamental limitation of public clients underscores the need for additional security mechanisms beyond traditional client authentication, paving the way for PKCE.
Key Findings
▶ Watch: Understanding the authorization code interception attack (8:00)
The central problem addressed by this talk is the Authorization Code Interception Attack against native clients leveraging OAuth 2.0. In this scenario, a malicious application, installed on a user's device, registers itself to handle the same custom URI scheme as a legitimate application. When the authorization server redirects the user's browser with the authorization code, the operating system might, due to priority or user interaction, deliver the code to the malicious app instead of the intended one. For public clients, where client credentials (secrets) cannot be trusted as confidential, the attacker can then easily exchange the intercepted authorization code for an access token, effectively impersonating the legitimate application and gaining unauthorized access to the user's resources.
The primary solution introduced and thoroughly explained is Proof Key for Code Exchange (PKCE), an extension to OAuth 2.0 specifically designed to protect public clients from this interception attack. PKCE introduces a "proof of possession" mechanism, ensuring that the same client application that initiated the authorization request is the one that exchanges the authorization code for an access token. This is achieved by binding the authorization request to the token exchange through a dynamically generated secret.
While PKCE significantly raises the bar for attackers, the talk also highlights a more sophisticated attack known as Pixie bypass via app impersonation. This advanced attack demonstrates that PKCE is not a "silver bullet." In this scenario, a malicious app not only intercepts the redirect URI but also initiates its own authorization flow, impersonating the legitimate app by using its client ID and redirect URI. The attacker generates their own PKCE code_challenge and code_verifier, and if successful in tricking the user into authorizing the request, they can then exchange the authorization code using their valid, self-generated code_verifier. This attack, however, is significantly more complex, requiring the attacker to achieve higher redirect priority, potentially steal client credentials (if the client isn't public), and most critically, employ phishing techniques to trick the user into granting consent to the malicious application. This finding emphasizes that security is a multi-layered defense, where PKCE addresses a specific technical vulnerability but doesn't eliminate the need for user vigilance and other security best practices.
Technical Deep Dive
▶ Watch: Pixie bypass via app impersonation attack (12:30)
The Authorization Code Interception Attack hinges on the ability of a malicious application to intercept the authorization code returned by the authorization server. Consider a typical OAuth 2.0 Authorization Code Flow for a native client:
- Client initiates authorization: The legitimate client app constructs an authorization request, including its
client_idand aredirect_uri, then directs the user's browser to the authorization server. - User authentication and consent: The user authenticates with the authorization server and, if satisfied, grants the requested permissions to the client app.
- Authorization code issuance: The authorization server issues a one-time authorization code.
- Redirect with code: The authorization server redirects the user's browser back to the
redirect_urispecified by the client, appending the authorization code as a query parameter (e.g.,my-app://callback?code=AUTH_CODE).
The vulnerability arises in step 4. Native operating systems (like Android or iOS) allow applications to register as handlers for specific custom URL schemes (e.g., my-app://) or claimed HTTPS schemes (e.g., https://app.example.com/callback). If a malicious app registers itself for the same scheme as the legitimate app, and for some reason gains a higher priority or is chosen by the user, it can intercept the AUTH_CODE. For public clients (mobile apps, SPAs), which cannot securely store a client_secret, the client_id is often publicly known or easily discoverable. With the intercepted AUTH_CODE and the client_id, the malicious app can then make a request to the authorization server's token endpoint, exchanging the code for an access_token and potentially a refresh_token. This grants the attacker full access to the user's resources as if they were the legitimate application.
PKCE (Proof Key for Code Exchange) was developed to counteract this specific vulnerability. It introduces a mechanism to cryptographically bind the authorization request to the token exchange, ensuring that only the client that initiated the request can complete the flow. This involves two new parameters:
code_verifier: A cryptographically random string generated by the client application for each authorization request. The specification recommends a length of 43-128 characters, composed of URL-safe[A-Z],[a-z],[0-9], and"-",".","_~"characters. The talk mentions a "32 octets sequence" from a random number generator, which is then Base64url encoded. This value must be kept secret by the client.code_challenge: A transformation of thecode_verifier. The most common and recommended transformation is S256, which involves hashing thecode_verifierusing SHA256 and then Base64url encoding the resulting hash. The talk states this occurs in "19% cases" but strongly implies it's the standard.
Here’s how PKCE modifies the Authorization Code Flow:
- Client generates PKCE parameters: Before initiating the authorization request, the client app generates a
code_verifier. It then computes thecode_challengefrom thecode_verifierusing the S256 method. - Authorization request with
code_challenge: The client sends the authorization request to the authorization server, including theclient_id,redirect_uri, and two new parameters:code_challengeandcode_challenge_method(e.g.,S256). The authorization server stores these values alongside theclient_id. - User authentication and consent: (Unchanged)
- Authorization code issuance and redirect: (Unchanged) The authorization server issues the authorization code and redirects the user back to the client's
redirect_uri. - Token exchange with
code_verifier: When the client receives the authorization code, it makes a POST request to the authorization server's token endpoint. This request includes theclient_id, the receivedauthorization_code, theredirect_uri, and crucially, the originalcode_verifier. - Server validation: The authorization server retrieves the stored
code_challengeandcode_challenge_methodassociated with theclient_idandauthorization_code. It then takes the receivedcode_verifierfrom the token exchange request, transforms it using the storedcode_challenge_method(e.g., SHA256 hash and Base64url encode), and compares the result to the storedcode_challenge. If they match, the server issues theaccess_tokenandrefresh_token. If they do not match, the request is rejected.
This validation step ensures that only the application possessing the original code_verifier (which generated the code_challenge in the first place) can successfully exchange the authorization code for tokens. Since the code_verifier is never sent in the browser redirect, an attacker intercepting the authorization_code would not have the necessary code_verifier to complete the token exchange. It is crucial that a new code_verifier and code_challenge pair is generated for each authorization request to prevent replay attacks.
However, the talk also introduces a more advanced attack: Pixie bypass via app impersonation. This attack scenario is significantly more complex and requires several conditions to be met:
- Malicious app installation and scheme registration: An attacker installs a malicious app on the user's device that registers for the same custom scheme as the legitimate app, potentially with higher priority.
- Client credential theft (if applicable): If the legitimate client is not a public client and has a
client_secret, the attacker might need to steal these credentials (e.g., via traffic sniffing), though the talk emphasizes that for public clients, this step is often unnecessary as theclient_idis public. - Malicious app initiates flow: The malicious app itself initiates an authorization flow, pretending to be the legitimate app by using the legitimate app's
client_idandredirect_uri. - Malicious app generates its own PKCE: The malicious app generates its own
code_verifierandcode_challengepair. It sends itscode_challengeandcode_challenge_methodin the authorization request to the authorization server. - User phishing: The most critical step. The malicious app must then trick the user into authenticating and granting consent to its initiated authorization request. This usually involves sophisticated phishing techniques, as the user is not expecting to initiate an authorization flow at that moment.
- Code interception and exchange: If the user falls for the phishing attempt, the authorization server issues the code, which is then redirected to the
redirect_uri. Due to the malicious app's scheme registration, it intercepts this code. Since it also generated the originalcode_verifierfor this specific flow, it can successfully exchange the intercepted code for anaccess_tokenusing its own validcode_verifier.
While this attack technically bypasses PKCE, it significantly increases the complexity for the attacker. It moves beyond simple code interception to requiring active user deception and, in some cases, credential theft. The talk highlights that the user initiating the flow (or being tricked into doing so) is a key differentiator from the simpler interception attack that PKCE primarily addresses.
Demo / Proof of Concept
▶ Watch: Mitigating Pixie bypass and deep linking (16:00)
The talk primarily focused on theoretical explanations and detailed flow diagrams of the attacks and Pixie's mechanism, rather than demonstrating a live proof of concept. Alina Boshchenko meticulously walked through the steps of the authorization code interception attack, the introduction of PKCE, and the more advanced PKCE bypass via app impersonation, illustrating each stage with conceptual diagrams and technical parameters. While no live demonstration of code or exploitation was shown, the detailed verbal descriptions and visual aids effectively conveyed the technical intricacies and implications of the vulnerabilities and their mitigations.
Defensive Implications
▶ Watch: Conclusion: Pixie's impact and limitations (19:45)
Implementing PKCE is no longer an optional security measure but a standard requirement for native OAuth 2.0 clients. However, as the "Pixie bypass via app impersonation" attack demonstrates, PKCE alone is not a silver bullet. A multi-layered defense strategy is crucial for truly robust security. Defenders should consider the following best practices:
- Always Use PKCE: This is foundational. Every native client implementing the OAuth 2.0 Authorization Code Flow must use PKCE. It provides a critical layer of protection against authorization code interception, which is a common and relatively easy attack without it. All major OAuth 2.0 providers widely adopt and support PKCE.
- Refresh Token Rotation: When using refresh tokens, implement refresh token rotation. This means that every time a refresh token is used to obtain a new access token, a new refresh token should also be issued. The old refresh token should be immediately revoked or have a very limited single-use lifespan. This minimizes the window of opportunity for an attacker if a refresh token is compromised, as it quickly becomes invalid.
- Avoid Embedded User Agents: Native applications must not use embedded user agents (e.g., custom in-app browsers like WebViews) for OAuth 2.0 authorization requests. Instead, they should launch the user's external user agent (the device's native browser, such as Chrome or Safari). Embedded user agents violate the principle of least privilege by giving the application access to the user's entire authentication profile (cookies, sessions) within that embedded browser, not just the specific credentials needed for the current authorization. Authorization servers may even detect and block requests originating from embedded user agents.
- Client ID and Secret per Application Instance (where possible): For confidential clients, or if your architecture allows for unique client registration per instance of a native app, consider generating different client IDs and secrets per application instance. This significantly complicates an attacker's task, as stealing credentials would require traffic sniffing for each individual instance, rather than having a single, universal credential.
- Leverage Claimed HTTPS Schemes for Redirect URIs (Mobile): On mobile platforms, prioritize claimed HTTPS schemes (e.g.,
https://app.example.com/callback) over custom URL schemes (e.g.,my-app://callback). Android's Application Links and iOS's Universal Links allow applications to register themselves as the authoritative handler for specific HTTPS domains. This mechanism requires the app to prove possession of the domain, making it much harder for a malicious app to impersonate a legitimate redirect URI.
- Understand Desktop Redirect Limitations: For desktop applications, loopback interfaces (e.g.,
http://127.0.0.1:port) and private URI schemes are typically used for redirects. While these are secure within the context of a trusted system, desktop security often relies more heavily on the user's ability to avoid installing malware. If a malicious app is already installed and has elevated privileges, it can potentially interfere with loopback redirects.
- Install Apps from Trusted Stores: A fundamental security practice is to advise users to install applications only from trusted application stores (e.g., Google Play, Apple App Store, official vendor websites). These platforms often enforce code signing and vetting processes, providing a baseline level of assurance regarding the publisher's identity and the app's integrity. This reduces the risk of installing malicious apps that could perform code interception or impersonation attacks.
- Avoid Client Credentials Flow for Native Clients: The Client Credentials Flow is designed for machine-to-machine communication where no user is involved and no consent is given. It is highly inappropriate and insecure for native clients, even with PKCE, as it bypasses the user consent step entirely. Using this flow for native apps would create a massive security hole, allowing any entity with the client credentials to gain access without user authorization.
- User Education and Awareness: Given the phishing component of the "Pixie bypass via app impersonation" attack, user education remains a crucial defensive layer. Users should be aware of unexpected authentication prompts, verify the legitimacy of requests for permissions, and understand the implications of granting access to applications.
By combining PKCE with these best practices, organizations can build a robust and resilient OAuth 2.0 implementation for native clients, significantly reducing their attack surface and enhancing overall security.
Key Takeaways
- PKCE is Essential for Native Client Security: Proof Key for Code Exchange (PKCE) is a mandatory extension for OAuth 2.0 when dealing with native applications (mobile and desktop) to protect against authorization code interception attacks, especially for public clients where client secrets cannot be trusted.
- PKCE Binds Request to Exchange: It works by cryptographically binding the initial authorization request (
code_challenge) to the final token exchange (code_verifier), ensuring that only the legitimate client that initiated the flow can complete it. - Public Clients are Inherently Vulnerable: Mobile and single-page applications are considered public clients and cannot securely store client secrets, making them prime targets for code interception if PKCE is not implemented.
- PKCE Isn't a Silver Bullet: While highly effective, PKCE can be bypassed by sophisticated "app impersonation" attacks that involve a malicious app initiating its own flow, generating its own PKCE parameters, and crucially, tricking the user via phishing.
- Multi-Layered Defense is Crucial: Robust security requires combining PKCE with other best practices, including refresh token rotation, avoiding embedded user agents, using claimed HTTPS schemes for redirects, and implementing per-instance client credentials where possible.
- User Vigilance and Trusted Sources: User education against phishing and the practice of installing applications only from trusted, code-signed sources remain vital components of the overall security posture, especially against advanced social engineering attacks.
About the Speaker(s)
Alina Boshchenko presented the talk "Elevating OAuth2.0 for Security of Native Clients" at the Hacktivity conference. She was introduced as the second speaker of the morning, following an opening by John. Her expertise lies in the intricacies of OAuth 2.0 and its security implications for modern application architectures.
Reviews
Dr. Zero (Offensive Security Researcher) — SOLID
Competent walkthrough of PKCE fundamentals and the authorization code interception problem. Useful for developers who haven't implemented OAuth 2.0 for native clients before, but offers nothing new to anyone who's read RFC 7636 or built a mobile app in the last five years.
Heather Calloway (CISO) — SOLID
Competent primer on PKCE for teams still catching up on OAuth 2.0 native client security. Not new ground for anyone who's been paying attention since RFC 7636, but a clean explanation of why public clients can't rely on secrets and what the bypass conditions actually look like.