From: Azharul Haque <haque@azharul.com>
To: Shan Shaji <s.shaji@proxmox.com>
Cc: pve-devel@lists.proxmox.com
Subject: Re: [PATCH 0/2] android: register OpenID Connect callback activity for #4281
Date: Wed, 9 Sep 2026 18:37:16 -0400 [thread overview]
Message-ID: <CAFCWXbg=Miu+1FdCNxSqUTq3Dj+MyGQeAjqs_9cu-0SfWpu6Eg@mail.gmail.com> (raw)
In-Reply-To: <DL3VHUZCB1DI.39LV5RFBUBD9N@proxmox.com>
Hi Shan,
Dug into the server side before replying. PVE's /access/openid/auth-url
and /access/openid/login (src/PVE/API2/OpenId.pm in pve-access-control)
only take {realm, redirect-url} and {state, code, redirect-url}
respectively, no code_challenge/code_verifier field in the API for a
client to plug into.
The actual PKCE handling lives in proxmox-openid (proxmox-rs), pve-rs's
openid module is just a thin binding on top. PrivateAuthState::new()
generates a fresh PkceCodeChallenge/PkceCodeVerifier pair per login
attempt, authorize_url() writes that state to disk via store_auth_state()
and only sends the challenge to the IdP, and
verify_authorization_code_userinfo() reads the same on-disk state back
by the state value and supplies the verifier itself when redeeming the
code. So PVE is already doing PKCE against the actual IdP, entirely
server-side, the verifier never leaves the server.
I don't think that fully closes the question though. Walk the flow: the
IdP redirects state+code straight to the app's custom scheme, the app
relays state+code to /access/openid/login over HTTPS, and PVE does the
real token exchange using the verifier it kept for itself. That last hop
means anyone holding a valid state+code pair can call /login and walk
away with a PVE ticket, the endpoint never asks the caller to prove it's
the same client the redirect was meant for.
That splits into two separate threats:
1. A different app intercepts the redirect itself (duplicate
custom-scheme registration). Auth Tabs and ASWebAuthenticationSession
close this, agreed, the redirect only ever reaches the requesting
app now.
2. state+code leaks after our app has them but before they reach PVE:
a crash reporter snapshotting the callback URL, a logging proxy in
between, or something on a compromised device reading it out from
under the app, an Android accessibility-service/clipboard scraper,
a rogue iOS keyboard extension or MDM profile, either way. Nothing
defends this today, not the platform APIs (they only guard the
redirect hop), not PVE's server-held verifier (it never checks
who's calling /login), and it's identical exposure on both
platforms since it's the same /login endpoint either way.
Real client-side PKCE would close that second gap: app generates a
verifier, sends the challenge at auth-url time, PVE forwards that
challenge to the IdP instead of its own, and /login requires the
verifier back before it'll redeem the code. But we can't add that from
the app alone, /login has no code_verifier field to send it to. It'd
need a pve-access-control change to accept a client-supplied
code_challenge/code_verifier and use that instead of (or in addition to)
the one it generates itself now.
PVE deployments aren't always kept behind a VPN or LAN, plenty get
exposed straight to the public internet by whoever's running them, so I'd
rather not leave that second gap open on the assumption it's unlikely.
Is a server-side change along those lines something you'd consider, or
is there a reason PVE holding the verifier itself instead of the client
is a deliberate design choice I'm missing?
Azharul
On Tue, Sep 1, 2026 at 5:30 AM Shan Shaji <s.shaji@proxmox.com> wrote:
> Hi Azharul,
>
> After reading through this [RFC 8252][0], it is a must to always use PKCE
> with native apps.
>
> > Public native app clients MUST implement the Proof Key for Code
> > Exchange (PKCE RFC7636 [1]) extension to OAuth, and authorization
> > servers MUST support PKCE for such clients, for the reasons detailed
> > in Section 8.1 [2].
>
> Section 8.1
>
> > The redirect URI options documented in Section 7 share the benefit
> > that only a native app on the same device or the app's own website
> > can receive the authorization code, which limits the attack surface.
> > However, code interception by a different native app running on the
> > same device may be possible.
>
> > A limitation of using private-use URI schemes for redirect URIs is
> > that multiple apps can typically register the same scheme, which
> > makes it indeterminate as to which app will receive the authorization
> > code.
>
> In Android, if there are multiple apps with the same URI schemes, it will
> show a disambiguation dialog [3], which will allow the user to select
> the app. AFAIU, this behaviour happens if the app is using custom tabs
> instead of the custom auth tabs which was released with chrome 132 [4][5].
>
> As for the custom auth tabs [7]:
>
> > Authentication strategies built on Custom Tabs offer a vast improvement
> > from prior solutions, but challenges still remain:
> >
> > - Communication between the browser tab and the app relies on
> > Activity intents, which can expose your app to potential
> > interference to your intent
> > - Using Activity intents to manage information transfer from the
> > tab is less idiomatic than using Android APIs
> >
> > Auth Tab solves these problems. A dedicated callback adds a layer of
> > security and eliminates the need for Activity intents.
>
> On iOS, as the plugin is using (iOS 12+) ASWebAuthenticationSession [6]
> only the calling app's session will receive the authentication callback.
>
> > `ASWebAuthenticationSession` ensures that only the calling app's session
> > receives the authentication callback, even when more than one app
> > registers the same callback URL scheme.
>
> Unless I have missed something, the new APIs does protect from code
> interception attack. Although, IMHO to be on the safe side we should
> also support PKCE at the client side. WDYT?
>
> - [0] https://www.rfc-editor.org/info/rfc8252/#section-6
> - [1] https://www.rfc-editor.org/info/rfc7636/#section-1.1
> - [2] https://www.rfc-editor.org/info/rfc8252/#section-8.1
> - [3]
> https://developer.android.com/training/app-links/create-deeplinks#how-deep
> - [4] https://developer.chrome.com/docs/android/custom-tabs/guide-auth-tab
> - [5] https://developer.chrome.com/blog/android-auth-tab
> - [6]
> https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession
> - [7]
> https://developer.chrome.com/blog/android-auth-tab#auth_tab_versus_custom_tabs
>
> On Mon Aug 10, 2026 at 7:40 AM CEST, Azharul Haque wrote:
> > The native Flutter "Proxmox VE Companion" app never implemented OpenID
> > Connect / OAuth realm login (bug #4281[0]): selecting an OAuth realm
> > just showed username/password fields that could never work.
> >
> > Android requires an app to explicitly declare, in AndroidManifest.xml,
> > which activity should handle a given custom URL scheme. This series
> > registers flutter_web_auth_2's CallbackActivity for the
> > com.proxmox.app.openid:// redirect scheme used by the OAuth flow, and
> > fixes it up to match a later rename of that scheme.
> >
> > No iOS-side changes are needed: ASWebAuthenticationSession resolves
> > the custom-scheme redirect at runtime without a static declaration
> > equivalent to this manifest entry.
> >
> > Depends on the companion proxmox_dart_api_client and
> > proxmox_login_manager series, which implement the OIDC API calls and
> > login-form/OAuth-flow UI respectively.
> >
> > Verified end-to-end against a real PVE server with an Authentik OIDC
> > realm, on both Android and iOS.
> >
> > [0] https://bugzilla.proxmox.com/show_bug.cgi?id=4281
> >
> > Azharul Haque (2):
> > fix #4281: android: register OpenID Connect callback activity
> > fix #4281: android: match renamed OpenID callback scheme
> >
> > android/app/src/main/AndroidManifest.xml | 16 ++
> > linux/flutter/generated_plugin_registrant.cc | 8 +
> > linux/flutter/generated_plugins.cmake | 2 +
> > pubspec.lock | 184 +++++++++++--------
> > 4 files changed, 138 insertions(+), 72 deletions(-)
>
>
>
prev parent reply other threads:[~2026-09-10 6:55 UTC|newest]
Thread overview: 41+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-10 5:40 [PATCH 0/2] android: register OpenID Connect callback activity for #4281 Azharul Haque
2026-08-10 5:40 ` [PATCH 1/2] fix #4281: android: register OpenID Connect callback activity Azharul Haque
2026-08-10 5:40 ` [PATCH 2/2] fix #4281: android: match renamed OpenID callback scheme Azharul Haque
2026-08-10 13:59 ` [PATCH 0/2] android: register OpenID Connect callback activity for #4281 Shan Shaji
2026-08-10 14:47 ` [PATCH v2 0/7] app: implement OpenID Connect (OAuth) realm login (#4281) Azharul Haque
2026-08-10 14:47 ` [PATCH dart-api-client v2 1/2] fix #4281: access: add `type` property to `PveAccessDomainModel` Azharul Haque
2026-08-20 9:17 ` Shan Shaji
2026-08-20 13:37 ` Azharul Haque
2026-08-20 14:39 ` Shan Shaji
2026-08-21 2:56 ` Azharul Haque
2026-08-21 8:00 ` Shan Shaji
2026-08-10 14:47 ` [PATCH dart-api-client v2 2/2] fix #4281: access: add OpenID Connect auth-url/login helpers Azharul Haque
2026-08-20 9:48 ` Shan Shaji
2026-08-20 13:41 ` Azharul Haque
2026-08-10 14:47 ` [PATCH login-manager v2 1/3] fix #4281: ui: add OpenID Connect login flow to login form Azharul Haque
2026-08-20 12:31 ` Shan Shaji
2026-08-10 14:47 ` [PATCH login-manager v2 2/3] fix #4281: ui: fix stale Continue button state on realm switch Azharul Haque
2026-08-10 14:47 ` [PATCH login-manager v2 3/3] fix #4281: ui: use a namespaced OpenID callback scheme Azharul Haque
2026-08-20 14:28 ` Shan Shaji
2026-08-10 14:47 ` [PATCH flutter-frontend v2 1/2] fix #4281: android: register OpenID Connect callback activity Azharul Haque
2026-08-20 14:01 ` Shan Shaji
2026-08-10 14:47 ` [PATCH flutter-frontend v2 2/2] fix #4281: android: match renamed OpenID callback scheme Azharul Haque
2026-08-21 3:41 ` [PATCH v3 00/10] app: implement OpenID Connect (OAuth) realm login (#4281) Azharul Haque
2026-08-21 3:41 ` [PATCH dart-api-client v3 1/2] fix #4281: access: add `type` property to `PveAccessDomainModel` Azharul Haque
2026-08-21 3:41 ` [PATCH dart-api-client v3 2/2] fix #4281: access: add OpenID Connect auth-url/login helpers Azharul Haque
2026-08-26 12:49 ` Shan Shaji
2026-08-21 3:41 ` [PATCH login-manager v3 1/5] fix #4281: deps: add flutter_web_auth_2 dependency Azharul Haque
2026-08-21 3:41 ` [PATCH login-manager v3 2/5] refactor: ui: factor out shared login tail into _finishLogin Azharul Haque
2026-08-26 13:41 ` Shan Shaji
2026-08-21 3:41 ` [PATCH login-manager v3 3/5] refactor: ui: split password form into its own widget Azharul Haque
2026-08-26 9:32 ` Shan Shaji
2026-08-21 3:41 ` [PATCH login-manager v3 4/5] fix #4281: ui: add OpenID Connect login flow to login form Azharul Haque
2026-08-21 3:41 ` [PATCH login-manager v3 5/5] fix #4281: ui: fix stale Continue button state on realm switch Azharul Haque
2026-08-26 10:24 ` Shan Shaji
2026-08-21 3:41 ` [PATCH flutter-frontend v3 1/3] chore: regenerate plugin registrant for flutter_web_auth_2 Azharul Haque
2026-08-21 3:41 ` [PATCH flutter-frontend v3 2/3] fix #4281: android: set taskAffinity="" on MainActivity Azharul Haque
2026-08-27 10:40 ` Shan Shaji
2026-08-21 3:41 ` [PATCH flutter-frontend v3 3/3] fix #4281: android: register OpenID Connect callback activity Azharul Haque
2026-08-26 11:41 ` [PATCH v3 00/10] app: implement OpenID Connect (OAuth) realm login (#4281) Shan Shaji
2026-09-01 9:30 ` [PATCH 0/2] android: register OpenID Connect callback activity for #4281 Shan Shaji
2026-09-09 22:37 ` Azharul Haque [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CAFCWXbg=Miu+1FdCNxSqUTq3Dj+MyGQeAjqs_9cu-0SfWpu6Eg@mail.gmail.com' \
--to=haque@azharul.com \
--cc=pve-devel@lists.proxmox.com \
--cc=s.shaji@proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox