v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
MEDIUM AI PREVALENCE: HIGH auth.kotlin.android.webview-oauth

An OAuth authorization URL is loaded inside an in-app WebView (webView.loadUrl("...authorize?client_id=...")).

Why AI tools produce this: AI coding tools generate this anti-pattern by default, it appears in a large share of AI-written auth code.

Why this matters

Embedded WebViews are an anti-pattern for OAuth: the host app can read the user's credentials and the session cookie, there is no shared SSO session with the system browser, and providers such as Google reject WebView auth outright (CWE-522). AI samples reach for a WebView because it is the simplest way to "show the login page".

Use an external user-agent instead: Chrome Custom Tabs (CustomTabsIntent) or, for full OAuth/PKCE, AppAuth's AuthorizationService.performAuthorizationRequest(...), which hands the flow to the system browser and returns via a redirect URI.

VULNERABLE
vulnerable.kt
import android.webkit.WebView

class LoginActivity {
    fun startLogin(webView: WebView) {
        // ruleid: auth.kotlin.android.webview-oauth
        webView.loadUrl("https://accounts.example.com/oauth/authorize?client_id=abc&response_type=code")
    }

    fun startAuthorize(wv: WebView) {
        // ruleid: auth.kotlin.android.webview-oauth
        wv.loadUrl("https://idp.example.com/authorize?client_id=xyz")
    }
}
SAFE
safe.kt
import android.net.Uri
import android.webkit.WebView
import androidx.browser.customtabs.CustomTabsIntent
import net.openid.appauth.AuthorizationService

class LoginActivity {
    // Custom Tabs external browser: safe
    fun startLogin(context: android.content.Context) {
        val intent = CustomTabsIntent.Builder().build()
        intent.launchUrl(context, Uri.parse("https://accounts.example.com/oauth/authorize?client_id=abc"))
    }

    // AppAuth system-browser flow: safe
    fun startAuthorize(service: AuthorizationService, request: net.openid.appauth.AuthorizationRequest) {
        service.performAuthorizationRequest(request, pendingIntent)
    }

    // WebView loading a non-auth page: safe
    fun showHelp(webView: WebView) {
        webView.loadUrl("https://example.com/help")
    }
}

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.kotlin.android.webview-oauth -- <reason>

References

https://developer.android.com/training/basics/intents/custom-tabs ↗https://github.com/openid/AppAuth-Android ↗https://cwe.mitre.org/data/definitions/522.html ↗