From f7fedb1beec71eb589811b510ead53654cc50c5c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:09:18 +0300 Subject: [PATCH 1/7] Developer guide: two figures for the sign-in stack Authentication and Identity is 306 lines with no picture in it, and two of the things it explains are structural rather than procedural, so prose has to carry them a sentence at a time. The first figure is the sentence "every provider-specific class is a thin layer on top of these primitives" drawn out: five provider classes over OidcClient over SystemBrowser, with each platform's browser surface named underneath. FirebaseAuth sits outside that column, because it is the one that is not OpenID Connect at all -- the ID token crosses over to it, the code path does not, and that distinction is easy to miss in the running text. The second is the five-step list under the quick start, as a flow across three lanes. It makes the leg the list does not emphasise visible: the token exchange goes straight from the app to the provider, carrying the PKCE verifier and no client secret, with the browser already finished. That is the part of the model that makes a public mobile client safe, and it is worth being able to point at. Flat SVG, presentation attributes only, no filters or CSS variables, so it survives the HTML build, asciidoctor-pdf and the website embed. Both rendered in headless Chrome and read back. Co-Authored-By: Claude Opus 5 (1M context) --- .../Authentication-And-Identity.asciidoc | 6 ++ .../img/oidc-provider-stack.svg | 64 ++++++++++++++++ .../developer-guide/img/oidc-sign-in-flow.svg | 74 +++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 docs/developer-guide/img/oidc-provider-stack.svg create mode 100644 docs/developer-guide/img/oidc-sign-in-flow.svg diff --git a/docs/developer-guide/Authentication-And-Identity.asciidoc b/docs/developer-guide/Authentication-And-Identity.asciidoc index f1005644017..006a37dcd73 100644 --- a/docs/developer-guide/Authentication-And-Identity.asciidoc +++ b/docs/developer-guide/Authentication-And-Identity.asciidoc @@ -10,6 +10,8 @@ The stack is rebuilt around two new primitives: Every provider-specific class is a thin layer on top of these primitives, so once you learn the underlying client you understand the whole stack. +image::img/oidc-provider-stack.svg[The provider classes sitting on OidcClient and SystemBrowser,scaledwidth=95%] + WARNING: The legacy `com.codename1.io.Oauth2` class is **deprecated**. It opens an in-app `WebBrowser`, which modern identity providers (Google, Apple, Microsoft, Facebook) now refuse to render -- they detect the embedded view and block the page. The new `OidcClient` works the same on every supported platform without that limitation. See <> for a migration recipe. === Why the change @@ -40,6 +42,10 @@ That call: . Exchanges the code for tokens on the discovered token endpoint. . Verifies `state` and `nonce`, decodes the ID token, and persists the tokens via the default `TokenStore`. +image::img/oidc-sign-in-flow.svg[The sign-in flow across the app the platform browser and the provider,scaledwidth=95%] + +The token exchange is the leg worth noticing: it goes straight from your app to the provider's token endpoint, with the PKCE verifier and no client secret. The browser has already done its job by then. + ==== Picking a redirect URI For mobile apps, use a *custom scheme* unique to your app, for example `com.example.app:/oauth2redirect`. Register the scheme with the OS via the build hints below so the system browser can hand the redirect back to your app. diff --git a/docs/developer-guide/img/oidc-provider-stack.svg b/docs/developer-guide/img/oidc-provider-stack.svg new file mode 100644 index 00000000000..5d7f51c7a80 --- /dev/null +++ b/docs/developer-guide/img/oidc-provider-stack.svg @@ -0,0 +1,64 @@ + + + Every provider class is a layer over the same two primitives + + GoogleConnect + system browser, + no native SDK + + + MicrosoftConnect + Entra ID, + any tenant + + + FacebookConnect + system browser, + no native SDK + + + Auth0Connect + a plain OIDC + provider + + + AppleSignIn + native on iOS 13+, + OIDC elsewhere + + + + + + FirebaseAuth + not OpenID Connect: Identity Toolkit REST + + + Federate Firebase by handing it an ID token + from one of the classes above -- the token + crosses over, the code path does not. + + OidcClient + discovery, PKCE (S256), state and nonce verification, code exchange, + the refresh-token grant, and persistence through TokenStore + + + + SystemBrowser + + iOS + ASWebAuthenticationSession + + + Android + Custom Tabs + + + JavaSE + the default browser + + + Web + the hosting browser + + diff --git a/docs/developer-guide/img/oidc-sign-in-flow.svg b/docs/developer-guide/img/oidc-sign-in-flow.svg new file mode 100644 index 00000000000..7cd05ebc9e1 --- /dev/null +++ b/docs/developer-guide/img/oidc-sign-in-flow.svg @@ -0,0 +1,74 @@ + + + + Your app + + The platform's sign-in surface + + The identity provider + + OidcClient.signIn() + discovery document, + PKCE verifier + challenge, + state and nonce + + SystemBrowser + ASWebAuthenticationSession (iOS), + Custom Tabs (Android), + default browser elsewhere + + Authorization endpoint + the user signs in here, + with a passkey if the + provider offers one + + Redirect + code + state sent to + your redirect URI + + Handed back to the app + the custom scheme or HTTPS URL + you registered with the OS + + Verify state + a mismatch aborts + before any token call + + Token exchange + code + PKCE verifier, + no client secret on + the device + + Token endpoint + returns id_token, + access_token and + refresh_token + + TokenStore + tokens persisted; + refreshIfExpired renews + them on next launch + + + + + + + + + + + + + + + + + + + the authorization request, + carrying the PKCE challenge + code + PKCE verifier + tokens, straight to the app -- + the browser is not in this leg + From cee463bf6b28405dbeaff1bfa9d6dcc343d6f807 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:14:21 +0300 Subject: [PATCH 2/7] Developer guide: draw the two paths that leave the OIDC stack The stack figure put every provider class on one bus into OidcClient, and two of them do not belong there. AppleSignIn.signIn() looks up the native interface first and calls signInNative whenever it reports supported, so on iOS 13+ neither OidcClient nor SystemBrowser is involved at all -- the OIDC route is the fallback for everywhere else. The figure now forks it, with the native sheet as its own destination. FirebaseAuth reaches its session by way of signInWithIdpIdToken, which posts id_token=..., and FacebookConnect.signIn returns OidcTokens whose getIdToken() is null because Facebook's flow issues an access token and nothing else. So "an ID token from one of the classes above" was an invitation to build a request Firebase rejects. The handoff now names the four providers that can supply one, and FacebookConnect's box says it issues none. The chapter says a thin-layer-over-the-primitives sentence and then spends 200 lines on the exceptions, so the two exits are now stated in the prose beside the figure rather than only drawn. Co-Authored-By: Claude Opus 5 (1M context) --- .../Authentication-And-Identity.asciidoc | 4 +- .../img/oidc-provider-stack.svg | 126 +++++++++--------- 2 files changed, 68 insertions(+), 62 deletions(-) diff --git a/docs/developer-guide/Authentication-And-Identity.asciidoc b/docs/developer-guide/Authentication-And-Identity.asciidoc index 006a37dcd73..2dad52436f5 100644 --- a/docs/developer-guide/Authentication-And-Identity.asciidoc +++ b/docs/developer-guide/Authentication-And-Identity.asciidoc @@ -10,7 +10,9 @@ The stack is rebuilt around two new primitives: Every provider-specific class is a thin layer on top of these primitives, so once you learn the underlying client you understand the whole stack. -image::img/oidc-provider-stack.svg[The provider classes sitting on OidcClient and SystemBrowser,scaledwidth=95%] +image::img/oidc-provider-stack.svg[The OpenID Connect stack and the two paths that leave it,scaledwidth=95%] + +Two places step outside it, and both are deliberate. `AppleSignIn` calls the native Apple sheet whenever the platform offers one, which on iOS 13+ means neither primitive is involved; the OpenID Connect path is what it falls back to everywhere else. And `FirebaseAuth` isn't an OpenID Connect provider at all -- you reach it by handing it an ID token you already obtained, which `FacebookConnect` can't give you because Facebook's flow issues an access token and nothing else. WARNING: The legacy `com.codename1.io.Oauth2` class is **deprecated**. It opens an in-app `WebBrowser`, which modern identity providers (Google, Apple, Microsoft, Facebook) now refuse to render -- they detect the embedded view and block the page. The new `OidcClient` works the same on every supported platform without that limitation. See <> for a migration recipe. diff --git a/docs/developer-guide/img/oidc-provider-stack.svg b/docs/developer-guide/img/oidc-provider-stack.svg index 5d7f51c7a80..ed400dccd25 100644 --- a/docs/developer-guide/img/oidc-provider-stack.svg +++ b/docs/developer-guide/img/oidc-provider-stack.svg @@ -1,64 +1,68 @@ - Every provider class is a layer over the same two primitives - - GoogleConnect - system browser, - no native SDK - - - MicrosoftConnect - Entra ID, - any tenant - - - FacebookConnect - system browser, - no native SDK - - - Auth0Connect - a plain OIDC - provider - - - AppleSignIn - native on iOS 13+, - OIDC elsewhere - - - - - - FirebaseAuth - not OpenID Connect: Identity Toolkit REST - - - Federate Firebase by handing it an ID token - from one of the classes above -- the token - crosses over, the code path does not. - - OidcClient - discovery, PKCE (S256), state and nonce verification, code exchange, - the refresh-token grant, and persistence through TokenStore - - - - SystemBrowser - - iOS - ASWebAuthenticationSession - - - Android - Custom Tabs - - - JavaSE - the default browser - - - Web - the hosting browser - + The OpenID Connect stack, and the two paths that leave it + + GoogleConnect + system browser, + no native SDK + + + MicrosoftConnect + Entra ID, + any tenant + + + FacebookConnect + OAuth 2.0 only -- + issues no ID token + + + Auth0Connect + a plain OIDC + provider + + + AppleSignIn + native on iOS 13+, + OIDC elsewhere + + + + + + FirebaseAuth + not OpenID Connect: Identity Toolkit REST. Federate it + with an ID token from Google, Microsoft, Apple or Auth0. + + + + The native Apple sheet + ASAuthorizationAppleIDProvider on iOS 13+. AppleSignIn + takes it whenever it can, and touches neither primitive. + + + + OidcClient + discovery, PKCE (S256), state and nonce verification, code exchange, + the refresh-token grant, and persistence through TokenStore + + + + SystemBrowser + + iOS + ASWebAuthenticationSession + + + Android + Custom Tabs + + + JavaSE + the default browser + + + Web + the hosting browser + From bff3564adf18e7430518a5e1d11404526ccab7e5 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:20:06 +0300 Subject: [PATCH 3/7] Developer guide: three corrections to the sign-in flow figure The entry point is authorize(). OidcClient has no signIn(), the quick start directly above the figure calls authorize(), and a reader translating the picture into code would have gone looking for a method that does not exist. The refresh token is conditional. The quick start asks for openid, email and profile and nothing else, so Google returns no refresh token and refreshIfExpired has nothing to renew -- which is why GoogleConnect.signIn adds access_type=offline and prompt=consent. The figure said the token endpoint returns one, flatly. And "no client secret" was too categorical. It is true of a provider that treats the app as a public client, which is the arrangement PKCE exists to make safe, but AppleSignIn's non-native path sets the webClientSecret and the Apple section says the exchange needs a JWT your server mints. Scoped in the figure and in the sentence beside it, which carried the same claim. Co-Authored-By: Claude Opus 5 (1M context) --- .../Authentication-And-Identity.asciidoc | 4 +++- docs/developer-guide/img/oidc-sign-in-flow.svg | 18 +++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/developer-guide/Authentication-And-Identity.asciidoc b/docs/developer-guide/Authentication-And-Identity.asciidoc index 2dad52436f5..004b8224e4f 100644 --- a/docs/developer-guide/Authentication-And-Identity.asciidoc +++ b/docs/developer-guide/Authentication-And-Identity.asciidoc @@ -46,7 +46,9 @@ That call: image::img/oidc-sign-in-flow.svg[The sign-in flow across the app the platform browser and the provider,scaledwidth=95%] -The token exchange is the leg worth noticing: it goes straight from your app to the provider's token endpoint, with the PKCE verifier and no client secret. The browser has already done its job by then. +The token exchange is the leg worth noticing: it goes straight from your app to the provider's token endpoint, carrying the PKCE verifier, and the browser has already done its job by then. A provider that treats your app as a public client wants no secret on that request, which is the arrangement PKCE exists to make safe. Apple is the exception on its non-native path and wants a `client_secret` JWT your server mints, which is what <> is about. + +Whether the token endpoint hands back a refresh token is up to the request: the quick start above asks for `openid`, `email` and `profile` and nothing more, so Google returns none and there is nothing for `refreshIfExpired` to renew. `GoogleConnect.signIn` adds `access_type=offline` and `prompt=consent` for exactly that reason. ==== Picking a redirect URI diff --git a/docs/developer-guide/img/oidc-sign-in-flow.svg b/docs/developer-guide/img/oidc-sign-in-flow.svg index 7cd05ebc9e1..ecf54359334 100644 --- a/docs/developer-guide/img/oidc-sign-in-flow.svg +++ b/docs/developer-guide/img/oidc-sign-in-flow.svg @@ -7,7 +7,7 @@ The identity provider - OidcClient.signIn() + OidcClient.authorize() discovery document, PKCE verifier + challenge, state and nonce @@ -35,19 +35,19 @@ before any token call Token exchange - code + PKCE verifier, - no client secret on - the device + code + PKCE verifier; + a public client sends + no secret Token endpoint - returns id_token, - access_token and - refresh_token + id_token and access_token, + plus refresh_token only if + offline access was asked for TokenStore tokens persisted; - refreshIfExpired renews - them on next launch + refreshIfExpired renews them + when a refresh token came back From 9bd02dcbdd4a299dfb488bff3128f37bc3fec65b Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:27:15 +0300 Subject: [PATCH 4/7] Developer guide: three more corrections, two of which the chapter shared JavaSE and the Web port do not use the default browser. OidcBrowserNativeImpl exists only under Ports/iOSPort and Ports/Android, so everywhere else SystemBrowser.authenticate falls back to BrowserWindow -- a JavaFX WebView on the desktop. The figure said "the default browser" because the chapter's own bullet does, three paragraphs above a WARNING about providers refusing embedded views. Both now say what happens, and why a simulator sign-in can be refused where a device one is not. Only the redirect URL crosses back. AppleSignIn's non-iOS path sets response_mode=form_post, so Apple POSTs code and state to the redirect URI, and OidcClient.handleRedirect parses the URL alone -- the sign-in fails with STATE_MISMATCH unless a page at that URI turns the POST back into a redirect. Said in the figure and spelled out beside it. And the nonce check is conditional: postToTokenEndpoint compares only when the ID token actually carries a nonce claim, so a token that omits one is accepted and persisted. The figure promised verification flatly; it now says when it happens. That last one is a weakness in OidcClient rather than in the guide, and worth a look on its own. Co-Authored-By: Claude Opus 5 (1M context) --- .../Authentication-And-Identity.asciidoc | 4 ++- .../img/oidc-provider-stack.svg | 27 +++++++------- .../developer-guide/img/oidc-sign-in-flow.svg | 35 ++++++++++--------- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/docs/developer-guide/Authentication-And-Identity.asciidoc b/docs/developer-guide/Authentication-And-Identity.asciidoc index 004b8224e4f..c84bf62512c 100644 --- a/docs/developer-guide/Authentication-And-Identity.asciidoc +++ b/docs/developer-guide/Authentication-And-Identity.asciidoc @@ -6,7 +6,7 @@ This chapter covers Codename One's modern sign-in stack: OpenID Connect, Sign in The stack is rebuilt around two new primitives: * `com.codename1.io.oidc.OidcClient` -- a full OpenID Connect / OAuth 2.0 client with PKCE, discovery, refresh and token persistence. -* `com.codename1.io.oidc.SystemBrowser` -- routes the sign-in step through the platform's hardened sign-in surface (`ASWebAuthenticationSession` on iOS, Android Custom Tabs, the user's default browser on JavaSE / Web). +* `com.codename1.io.oidc.SystemBrowser` -- routes the sign-in step through the platform's hardened sign-in surface where one exists: `ASWebAuthenticationSession` on iOS and Android Custom Tabs. JavaSE and the Web port have no such surface, so there it falls back to an in-app `BrowserWindow` -- a JavaFX `WebView` on the desktop. That's fine for development, and it's the reason a provider that blocks embedded views can refuse a simulator sign-in that works on a device. Every provider-specific class is a thin layer on top of these primitives, so once you learn the underlying client you understand the whole stack. @@ -48,6 +48,8 @@ image::img/oidc-sign-in-flow.svg[The sign-in flow across the app the platform br The token exchange is the leg worth noticing: it goes straight from your app to the provider's token endpoint, carrying the PKCE verifier, and the browser has already done its job by then. A provider that treats your app as a public client wants no secret on that request, which is the arrangement PKCE exists to make safe. Apple is the exception on its non-native path and wants a `client_secret` JWT your server mints, which is what <> is about. +Only the redirect URL crosses back from the browser, which is why Apple's non-iOS path needs a server in front of it: `AppleSignIn` asks for `response_mode=form_post`, so Apple POSTs `code` and `state` to your redirect URI rather than putting them in the query string, and `OidcClient` parses the URL alone. The page at that URI has to turn the POST into a redirect carrying the same parameters, or the sign-in fails with `STATE_MISMATCH`. + Whether the token endpoint hands back a refresh token is up to the request: the quick start above asks for `openid`, `email` and `profile` and nothing more, so Google returns none and there is nothing for `refreshIfExpired` to renew. `GoogleConnect.signIn` adds `access_type=offline` and `prompt=consent` for exactly that reason. ==== Picking a redirect URI diff --git a/docs/developer-guide/img/oidc-provider-stack.svg b/docs/developer-guide/img/oidc-provider-stack.svg index ed400dccd25..a9447c2888a 100644 --- a/docs/developer-guide/img/oidc-provider-stack.svg +++ b/docs/developer-guide/img/oidc-provider-stack.svg @@ -1,5 +1,5 @@ - - + + The OpenID Connect stack, and the two paths that leave it GoogleConnect @@ -43,26 +43,29 @@ OidcClient - discovery, PKCE (S256), state and nonce verification, code exchange, - the refresh-token grant, and persistence through TokenStore + discovery, PKCE (S256), state verification, a nonce check when the ID + token carries one, code exchange, the refresh-token grant, and + persistence through TokenStore SystemBrowser - + iOS - ASWebAuthenticationSession + ASWebAuthenticationSession - + Android - Custom Tabs + Custom Tabs - + JavaSE - the default browser + an in-app BrowserWindow + no hardened surface exists - + Web - the hosting browser + an in-app BrowserWindow + no hardened surface exists diff --git a/docs/developer-guide/img/oidc-sign-in-flow.svg b/docs/developer-guide/img/oidc-sign-in-flow.svg index ecf54359334..380495e5411 100644 --- a/docs/developer-guide/img/oidc-sign-in-flow.svg +++ b/docs/developer-guide/img/oidc-sign-in-flow.svg @@ -15,24 +15,27 @@ SystemBrowser ASWebAuthenticationSession (iOS), Custom Tabs (Android), - default browser elsewhere + an in-app BrowserWindow elsewhere Authorization endpoint the user signs in here, with a passkey if the provider offers one - - Redirect - code + state sent to - your redirect URI - - Handed back to the app - the custom scheme or HTTPS URL - you registered with the OS - - Verify state - a mismatch aborts - before any token call + + Redirect + code + state sent to + your redirect URI, in the + query string + + Handed back to the app + the custom scheme or HTTPS URL + you registered with the OS. Only the + URL crosses back, never a POST body + + Verify state + parsed out of that URL; + a mismatch aborts before + any token call Token exchange code + PKCE verifier; @@ -52,13 +55,13 @@ - - + + - + From d194b3c90ce1bc4b7d042b703e68b6a9636b3308 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:37:35 +0300 Subject: [PATCH 5/7] Developer guide: the Web port is not the desktop fallback, and Apple needs an App Link on Android Grouping JavaSE and the Web port together was wrong in the direction that matters. HTML5Implementation.createNativeBrowserWindow returns an HTML5BrowserWindow, whose show() calls Window.current().open() -- a real top-level browser window, not an embedded view, so the provider-rejection warning that applies to JavaSE's JavaFX WebView does not apply to it. The two are now separate in the prose and in the figure. And Apple's non-iOS path needs more than the POST-to-redirect bridge on Android: the Services ID callback is an HTTPS URL, and OidcBrowserNativeImpl hands control back by matching the scheme of the redirect it is waiting for against the arriving intent, so the custom scheme the chapter registers earlier is not what receives it. That callback host needs its own verified App Link, which is now said where the Services ID setup is described. Co-Authored-By: Claude Opus 5 (1M context) --- docs/developer-guide/Authentication-And-Identity.asciidoc | 4 +++- docs/developer-guide/img/oidc-provider-stack.svg | 8 ++++---- docs/developer-guide/img/oidc-sign-in-flow.svg | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/developer-guide/Authentication-And-Identity.asciidoc b/docs/developer-guide/Authentication-And-Identity.asciidoc index c84bf62512c..304ac24c5f1 100644 --- a/docs/developer-guide/Authentication-And-Identity.asciidoc +++ b/docs/developer-guide/Authentication-And-Identity.asciidoc @@ -6,7 +6,7 @@ This chapter covers Codename One's modern sign-in stack: OpenID Connect, Sign in The stack is rebuilt around two new primitives: * `com.codename1.io.oidc.OidcClient` -- a full OpenID Connect / OAuth 2.0 client with PKCE, discovery, refresh and token persistence. -* `com.codename1.io.oidc.SystemBrowser` -- routes the sign-in step through the platform's hardened sign-in surface where one exists: `ASWebAuthenticationSession` on iOS and Android Custom Tabs. JavaSE and the Web port have no such surface, so there it falls back to an in-app `BrowserWindow` -- a JavaFX `WebView` on the desktop. That's fine for development, and it's the reason a provider that blocks embedded views can refuse a simulator sign-in that works on a device. +* `com.codename1.io.oidc.SystemBrowser` -- routes the sign-in step through the platform's hardened sign-in surface where one exists: `ASWebAuthenticationSession` on iOS and Android Custom Tabs. Elsewhere it falls back to `BrowserWindow`, and what that means differs: the Web port opens a real top-level browser window, while JavaSE renders a JavaFX `WebView` inside the app. The desktop one is an embedded view, so a provider that blocks those can refuse a simulator sign-in that works on a device. Every provider-specific class is a thin layer on top of these primitives, so once you learn the underlying client you understand the whole stack. @@ -116,6 +116,8 @@ NOTE: Apple only returns the user's name and email on the *first* authorization. Apple requires a separate *Services ID* (a "web client") for non-iOS environments and a `client_secret` JWT generated by your server. The recipe lives at <>. +On Android there is a second thing to arrange. The Services ID callback is an HTTPS URL, and the Android sign-in surface hands control back by matching the scheme of the redirect it's waiting for, so the `com.example.app` custom scheme registered earlier in this chapter does nothing for it. The callback host and path need their own verified App Link -- an `android.xintent_filter` for `android:scheme="https"` on that host, and an `assetlinks.json` served from it. See <> for both. + [[apple-services-id-setup]] ==== One-time Apple setup diff --git a/docs/developer-guide/img/oidc-provider-stack.svg b/docs/developer-guide/img/oidc-provider-stack.svg index a9447c2888a..10b1222e259 100644 --- a/docs/developer-guide/img/oidc-provider-stack.svg +++ b/docs/developer-guide/img/oidc-provider-stack.svg @@ -60,12 +60,12 @@ JavaSE - an in-app BrowserWindow - no hardened surface exists + an in-app WebView + an embedded view; providers may refuse it Web - an in-app BrowserWindow - no hardened surface exists + a browser popup window + a real window, not embedded diff --git a/docs/developer-guide/img/oidc-sign-in-flow.svg b/docs/developer-guide/img/oidc-sign-in-flow.svg index 380495e5411..4224cb4bdc0 100644 --- a/docs/developer-guide/img/oidc-sign-in-flow.svg +++ b/docs/developer-guide/img/oidc-sign-in-flow.svg @@ -15,7 +15,7 @@ SystemBrowser ASWebAuthenticationSession (iOS), Custom Tabs (Android), - an in-app BrowserWindow elsewhere + a BrowserWindow elsewhere Authorization endpoint the user signs in here, From dd17691a037571941232b17cde16c210e8e1912d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:45:21 +0300 Subject: [PATCH 6/7] Developer guide: name the popup constraint, and point at a recipe that exists The Web port's BrowserWindow is a window.open() call, and it runs after the discovery request comes back rather than inside the click that started the sign-in, so a browser that has expired the transient activation blocks it -- and HTML5BrowserWindow.show() calls addEventListener on the result with no null check. Said where the Web fallback is described, with the two ways out. The Apple-on-Android note pointed at the deep-link chapter for an App Link recipe that chapter only named. It now carries the filter, so this cross-reference goes somewhere useful, and the note says what to look for -- autoVerify, the https scheme and host, and that the hint takes one string so a second filter shares it. Co-Authored-By: Claude Opus 5 (1M context) --- docs/developer-guide/Authentication-And-Identity.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/developer-guide/Authentication-And-Identity.asciidoc b/docs/developer-guide/Authentication-And-Identity.asciidoc index 304ac24c5f1..74f616608d7 100644 --- a/docs/developer-guide/Authentication-And-Identity.asciidoc +++ b/docs/developer-guide/Authentication-And-Identity.asciidoc @@ -6,7 +6,7 @@ This chapter covers Codename One's modern sign-in stack: OpenID Connect, Sign in The stack is rebuilt around two new primitives: * `com.codename1.io.oidc.OidcClient` -- a full OpenID Connect / OAuth 2.0 client with PKCE, discovery, refresh and token persistence. -* `com.codename1.io.oidc.SystemBrowser` -- routes the sign-in step through the platform's hardened sign-in surface where one exists: `ASWebAuthenticationSession` on iOS and Android Custom Tabs. Elsewhere it falls back to `BrowserWindow`, and what that means differs: the Web port opens a real top-level browser window, while JavaSE renders a JavaFX `WebView` inside the app. The desktop one is an embedded view, so a provider that blocks those can refuse a simulator sign-in that works on a device. +* `com.codename1.io.oidc.SystemBrowser` -- routes the sign-in step through the platform's hardened sign-in surface where one exists: `ASWebAuthenticationSession` on iOS and Android Custom Tabs. Elsewhere it falls back to `BrowserWindow`, and what that means differs: the Web port opens a real top-level browser window, while JavaSE renders a JavaFX `WebView` inside the app. The desktop one is an embedded view, so a provider that blocks those can refuse a simulator sign-in that works on a device. The Web one has a constraint of its own -- it's a `window.open()` call, and it happens after the discovery request comes back rather than inside the click that started the sign-in, so a browser that has already expired the transient activation blocks it. Tell your users to allow popups for the site, or start the flow from an `OidcClient` you configured ahead of time instead of `discover()`. Every provider-specific class is a thin layer on top of these primitives, so once you learn the underlying client you understand the whole stack. @@ -116,7 +116,7 @@ NOTE: Apple only returns the user's name and email on the *first* authorization. Apple requires a separate *Services ID* (a "web client") for non-iOS environments and a `client_secret` JWT generated by your server. The recipe lives at <>. -On Android there is a second thing to arrange. The Services ID callback is an HTTPS URL, and the Android sign-in surface hands control back by matching the scheme of the redirect it's waiting for, so the `com.example.app` custom scheme registered earlier in this chapter does nothing for it. The callback host and path need their own verified App Link -- an `android.xintent_filter` for `android:scheme="https"` on that host, and an `assetlinks.json` served from it. See <> for both. +On Android there is a second thing to arrange. The Services ID callback is an HTTPS URL, and the Android sign-in surface hands control back by matching the scheme of the redirect it's waiting for, so the `com.example.app` custom scheme registered earlier in this chapter does nothing for it. The callback host and path need their own verified App Link: an `android.xintent_filter` carrying an `android:autoVerify="true"` filter for `android:scheme="https"` on that host, and an `assetlinks.json` served from it. <> has both, and says how to put that filter and the custom-scheme one in a single hint value -- the hint holds one string, so an app doing both needs them together. [[apple-services-id-setup]] From 288ea6225e18033ea7d8c4d4bf4e2fb1ecfbac35 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:48:50 +0300 Subject: [PATCH 7/7] Developer guide: a Web callback has to come back to the app's own origin HTML5BrowserWindow watches the popup by reading win.getLocation() on every load, and the same-origin policy throws once the popup has navigated to another host. The catch falls back to the URL it already had -- the authorization URL -- so SystemBrowser never sees a redirect that matches and authorize() never completes. Silent, and it applies to the Apple form_post bridge, which the chapter had just told the reader to host somewhere. Co-Authored-By: Claude Opus 5 (1M context) --- docs/developer-guide/Authentication-And-Identity.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer-guide/Authentication-And-Identity.asciidoc b/docs/developer-guide/Authentication-And-Identity.asciidoc index 74f616608d7..48bce537dc6 100644 --- a/docs/developer-guide/Authentication-And-Identity.asciidoc +++ b/docs/developer-guide/Authentication-And-Identity.asciidoc @@ -6,7 +6,7 @@ This chapter covers Codename One's modern sign-in stack: OpenID Connect, Sign in The stack is rebuilt around two new primitives: * `com.codename1.io.oidc.OidcClient` -- a full OpenID Connect / OAuth 2.0 client with PKCE, discovery, refresh and token persistence. -* `com.codename1.io.oidc.SystemBrowser` -- routes the sign-in step through the platform's hardened sign-in surface where one exists: `ASWebAuthenticationSession` on iOS and Android Custom Tabs. Elsewhere it falls back to `BrowserWindow`, and what that means differs: the Web port opens a real top-level browser window, while JavaSE renders a JavaFX `WebView` inside the app. The desktop one is an embedded view, so a provider that blocks those can refuse a simulator sign-in that works on a device. The Web one has a constraint of its own -- it's a `window.open()` call, and it happens after the discovery request comes back rather than inside the click that started the sign-in, so a browser that has already expired the transient activation blocks it. Tell your users to allow popups for the site, or start the flow from an `OidcClient` you configured ahead of time instead of `discover()`. +* `com.codename1.io.oidc.SystemBrowser` -- routes the sign-in step through the platform's hardened sign-in surface where one exists: `ASWebAuthenticationSession` on iOS and Android Custom Tabs. Elsewhere it falls back to `BrowserWindow`, and what that means differs: the Web port opens a real top-level browser window, while JavaSE renders a JavaFX `WebView` inside the app. The desktop one is an embedded view, so a provider that blocks those can refuse a simulator sign-in that works on a device. The Web one has a constraint of its own -- it's a `window.open()` call, and it happens after the discovery request comes back rather than inside the click that started the sign-in, so a browser that has already expired the transient activation blocks it. Tell your users to allow popups for the site, or start the flow from an `OidcClient` you configured ahead of time instead of `discover()`. And point the redirect URI at a page on the app's own origin: the port watches the popup by reading its location, the same-origin policy blocks that read once the popup navigates elsewhere, and the fallback reports the authorization URL again -- so a callback hosted anywhere else never gets recognized and `authorize()` simply never completes. That applies to Apple's `form_post` bridge too, which has to live on that origin rather than beside the Services ID. Every provider-specific class is a thin layer on top of these primitives, so once you learn the underlying client you understand the whole stack.