Skip to content

Add WebAuthn / passkey client + Auth0 / Firebase passkey helpers#5039

Merged
shai-almog merged 4 commits into
masterfrom
feat/webauthn
May 25, 2026
Merged

Add WebAuthn / passkey client + Auth0 / Firebase passkey helpers#5039
shai-almog merged 4 commits into
masterfrom
feat/webauthn

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

Extends the OidcClient identity stack (#5018) with a portable WebAuthn / passkey client and provider passkey helpers.

  • New com.codename1.io.webauthn package: WebAuthnClient (W3C JSON wire format), WebAuthnNative SPI, options + response wrappers (PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, PublicKeyCredential), WebAuthnException with W3C-named codes
  • Native bindings: ASAuthorizationPlatformPublicKeyCredentialProvider on iOS 16+, androidx.credentials.CredentialManager on Android API 28+
  • Provider helpers: Auth0Connect.signInWithPasskey / .registerPasskey (Auth0 WebAuthn grant), FirebaseAuth.signInWithPasskey / .registerPasskey (Identity Platform v2 REST endpoints)
  • 14 new WebAuthnCoreTest assertions; the 30 existing identity-stack tests still pass
  • New Passkeys / WebAuthn section in the Authentication and Identity developer-guide chapter, including the "you already get passkeys via OIDC" guidance and the W3C error-code mapping

Why now

Most CN1 apps that sign users in via OidcClient against Google / Apple / Microsoft / Auth0 / Firebase already get passkeys transparently -- the IdP handles the ceremony, OIDC just hands you the resulting tokens. This PR lands the surface for the cases that don't have that path:

  1. Apps with their own relying-party backend that want passwordless sign-in
  2. Apps that want to drive Auth0 or Firebase passkeys directly (the Auth0 WebAuthn grant and the Firebase Identity Platform v2 passkey endpoints both want client-side WebAuthn ceremonies, not a redirect flow)

Architecture

Mirrors the OIDC stack:

  • Java in core is platform-neutral; the actual passkey call goes through a WebAuthnNative SPI
  • The platform port supplies the implementation and registers it via WebAuthnClient.setProvider(...) from a generated init stub
  • IPhoneBuilder / AndroidGradleBuilder only flip the bridge on when the classpath scanner sees com.codename1.io.webauthn.* references, so apps that never use passkeys ship without the symbols or extra Gradle deps
  • Data interchange is W3C-compliant JSON in both directions, so the response can be POSTed verbatim to any server-side WebAuthn library (webauthn4j, @simplewebauthn/server, webauthn-rs, etc.)

Test plan

  • WebAuthnCoreTest (14 assertions) -- pure-JVM coverage of options / response parsing, builder round-trip, exception codes, async dispatch with a stub provider
  • Full identity-stack regression: 44 tests across OidcCoreTest, WebAuthnCoreTest, Oauth2Test, Oauth2RefreshTokenRequestTest, GoogleConnectTest, FacebookConnectTest, LoginTest, Login1Test, LoginExtrasTest all pass
  • Maven plugin compiles (verifies IPhoneBuilder + AndroidGradleBuilder scanner changes)
  • Android port packages (WebAuthnNativeImpl.java ships in android_port_sources.jar)
  • clang -fsyntax-only on CN1WebAuthn.m against the iPhoneOS SDK in both stubs and full configurations
  • End-to-end registration / sign-in on an iOS 16+ device with an Associated Domains entitlement against a test relying-party server (manual; not run as part of CI)
  • End-to-end registration / sign-in on an Android API 28+ device with assetlinks.json published (manual)
  • Auth0 passkey grant against a tenant with the WebAuthn grant enabled (manual; requires tenant config)
  • Firebase Identity Platform passkey enrolment + sign-in (manual; requires the Identity Platform upgrade tier)

🤖 Generated with Claude Code

Extends the OidcClient identity stack (#5018) with a portable WebAuthn
client. Most CN1 apps that sign users in via OidcClient already get
passkeys for free (the IdP handles the ceremony, OIDC just delivers the
tokens) -- this lands the surface for apps that talk to their own
relying-party backend, or that want to drive Auth0 / Firebase passkeys
directly.

* New com.codename1.io.webauthn package: WebAuthnClient (create/get
  with the W3C JSON wire format), WebAuthnNative SPI,
  PublicKeyCredentialCreationOptions / RequestOptions / response
  wrapper with verbatim toJson() round-trip, WebAuthnException with
  W3C-named codes (NotAllowedError, InvalidStateError, SecurityError,
  etc.).
* iOS port: ASAuthorizationPlatformPublicKeyCredentialProvider impl
  (iOS 16+) in Ports/iOSPort/nativeSources/CN1WebAuthn.m. Gated behind
  the existing CN1_INCLUDE_WEBAUTHN define so apps that never use
  passkeys ship without the symbols. AuthenticationServices.framework
  was already auto-linked by the OIDC scanner branch; IPhoneBuilder
  now also flips the CN1_INCLUDE_WEBAUTHN define when the classpath
  scanner sees com.codename1.io.webauthn.* references.
* Android port: androidx.credentials.CredentialManager impl via
  reflection (so the port itself doesn't need androidx.credentials
  on its compile classpath). AndroidGradleBuilder auto-injects
  androidx.credentials:credentials + credentials-play-services-auth
  when the app references the WebAuthn classes; versions can be
  overridden via android.credentialsVersion build hint.
* Provider passkey helpers: Auth0Connect.signInWithPasskey /
  registerPasskey (Auth0 WebAuthn grant_type), FirebaseAuth.
  signInWithPasskey / registerPasskey (Identity Platform v2 passkey
  REST endpoints). Both drive WebAuthnClient under the hood and
  return tokens through the existing OidcTokens / FirebaseUser model.
* Tests: 14 new WebAuthnCoreTest assertions (options parsing,
  response parsing, builder round-trip, exception code mapping,
  async dispatch with a stub provider). Async tests use a
  CountDownLatch wired through .ready/.except instead of
  AsyncResource.get(timeout) to avoid the missed-notify race in the
  observer/wait path when the worker thread races against the
  registration.
* Docs: new "Passkeys / WebAuthn" section in the Authentication and
  Identity chapter covering when to use the API, platform
  requirements (Associated Domains on iOS 16+, Digital Asset Links
  on Android API 28+), the Auth0 and Firebase passkey recipes, and
  the W3C error-code mapping.
* CI: extended .github/workflows/identity-stack.yml path filters,
  unit-test list, Android bundle verification, and clang
  -fsyntax-only step to cover the new WebAuthn sources.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 25, 2026

Compared 20 screenshots: 20 matched.
✅ JavaScript-port screenshot tests passed.

@github-actions
Copy link
Copy Markdown
Contributor

Cloudflare Preview

The WebAuthn stack ships in the same 7.0.245 release as the OidcClient
identity stack (#5018); the original 7.0.246 tag was off by one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 25, 2026

Compared 110 screenshots: 110 matched.

Native Android coverage

  • 📊 Line coverage: 11.90% (6835/57442 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.67% (34291/354705), branch 4.20% (1416/33724), complexity 5.21% (1685/32362), method 9.01% (1365/15142), class 14.49% (304/2098)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 11.90% (6835/57442 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.67% (34291/354705), branch 4.20% (1416/33724), complexity 5.21% (1685/32362), method 9.01% (1365/15142), class 14.49% (304/2098)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 644.000 ms
Base64 CN1 encode 138.000 ms
Base64 encode ratio (CN1/native) 0.214x (78.6% faster)
Base64 native decode 745.000 ms
Base64 CN1 decode 269.000 ms
Base64 decode ratio (CN1/native) 0.361x (63.9% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 25, 2026

Compared 110 screenshots: 110 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 162 seconds

Build and Run Timing

Metric Duration
Simulator Boot 58000 ms
Simulator Boot (Run) 1000 ms
App Install 11000 ms
App Launch 2000 ms
Test Execution 292000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 462.000 ms
Base64 CN1 encode 1420.000 ms
Base64 encode ratio (CN1/native) 3.074x (207.4% slower)
Base64 native decode 282.000 ms
Base64 CN1 decode 980.000 ms
Base64 decode ratio (CN1/native) 3.475x (247.5% slower)
Base64 SIMD encode 458.000 ms
Base64 encode ratio (SIMD/native) 0.991x (0.9% faster)
Base64 encode ratio (SIMD/CN1) 0.323x (67.7% faster)
Base64 SIMD decode 443.000 ms
Base64 decode ratio (SIMD/native) 1.571x (57.1% slower)
Base64 decode ratio (SIMD/CN1) 0.452x (54.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 59.000 ms
Image createMask (SIMD on) 9.000 ms
Image createMask ratio (SIMD on/off) 0.153x (84.7% faster)
Image applyMask (SIMD off) 136.000 ms
Image applyMask (SIMD on) 57.000 ms
Image applyMask ratio (SIMD on/off) 0.419x (58.1% faster)
Image modifyAlpha (SIMD off) 194.000 ms
Image modifyAlpha (SIMD on) 55.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.284x (71.6% faster)
Image modifyAlpha removeColor (SIMD off) 205.000 ms
Image modifyAlpha removeColor (SIMD on) 79.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.385x (61.5% faster)
Image PNG encode (SIMD off) 1766.000 ms
Image PNG encode (SIMD on) 902.000 ms
Image PNG encode ratio (SIMD on/off) 0.511x (48.9% faster)
Image JPEG encode 479.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 25, 2026

Compared 110 screenshots: 110 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 321 seconds

Build and Run Timing

Metric Duration
Simulator Boot 64000 ms
Simulator Boot (Run) 1000 ms
App Install 12000 ms
App Launch 15000 ms
Test Execution 270000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1135.000 ms
Base64 CN1 encode 1691.000 ms
Base64 encode ratio (CN1/native) 1.490x (49.0% slower)
Base64 native decode 762.000 ms
Base64 CN1 decode 1369.000 ms
Base64 decode ratio (CN1/native) 1.797x (79.7% slower)
Base64 SIMD encode 579.000 ms
Base64 encode ratio (SIMD/native) 0.510x (49.0% faster)
Base64 encode ratio (SIMD/CN1) 0.342x (65.8% faster)
Base64 SIMD decode 716.000 ms
Base64 decode ratio (SIMD/native) 0.940x (6.0% faster)
Base64 decode ratio (SIMD/CN1) 0.523x (47.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 56.000 ms
Image createMask (SIMD on) 9.000 ms
Image createMask ratio (SIMD on/off) 0.161x (83.9% faster)
Image applyMask (SIMD off) 139.000 ms
Image applyMask (SIMD on) 89.000 ms
Image applyMask ratio (SIMD on/off) 0.640x (36.0% faster)
Image modifyAlpha (SIMD off) 195.000 ms
Image modifyAlpha (SIMD on) 74.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.379x (62.1% faster)
Image modifyAlpha removeColor (SIMD off) 1594.000 ms
Image modifyAlpha removeColor (SIMD on) 85.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.053x (94.7% faster)
Image PNG encode (SIMD off) 1379.000 ms
Image PNG encode (SIMD on) 940.000 ms
Image PNG encode ratio (SIMD on/off) 0.682x (31.8% faster)
Image JPEG encode 707.000 ms

shai-almog and others added 2 commits May 25, 2026 08:02
The first CI run on PR #5039 turned up three categorized issues, all
contained to the new WebAuthn / passkey surface.

* SpotBugs SIC_INNER_SHOULD_BE_STATIC_ANON (5×) in FirebaseAuth's
  passkey helpers. The flagged anonymous SuccessCallback /
  ConnectionRequest bodies only referenced captured locals plus static
  utilities, so SpotBugs flagged them as candidates for promotion to
  named static nested classes. Refactored each body into a private
  instance method (`onPasskeyEnrollmentStart`, `onPasskeySignInStart`,
  `completeFromMap`, `handlePostJsonRawResponse`) that the callback
  delegates to. This matches the existing FirebaseAuth#enqueue
  pattern, which also uses an anonymous ConnectionRequest body that
  pins the enclosing instance via a `persist(u)` call. Added an
  `ErrorForwarder` named static class for the repeated
  `.except(throwable -> out.error(throwable))` boilerplate so the
  call sites stay readable.

* Vale (8×) in the new Passkeys / WebAuthn section. Replaced the
  "for free" cliche; de-hyphenated "auto-linked" / "auto-injected" per
  Microsoft.Auto style; switched four "does not" / "could not" to
  contractions.

* LanguageTool (11×). Added `IdP`, `webauthn`, `[Pp]asskey(s)?` to
  the developer-guide accept list (technical terminology). Replaced
  the seven `->` HTML-entity arrows in the Auth0 / Firebase console
  navigation paths with the Unicode `→` (`&#8594;`) the rule
  suggests. Fixed the British "enrol" / "enrols" spellings to the
  American "enroll" / "enrolls" the dictionary prefers.

44/44 identity-stack tests still pass and `vale` reports zero issues
on the chapter locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second CI pass on PR #5039 turned up PMD warnings that the project
treats as build-breaking:

* MissingOverride on the two `run()` methods in WebAuthnClient's
  CreateRunnable / GetRunnable named static classes -- both implement
  Runnable.run() and were missing the annotation.
* ControlStatementBraces (7×) on single-line `if (cond) stmt;` shapes
  in the JSON-building helpers I added to Auth0Connect (separator
  emission in joinScopes, mapToJson, mapToFlatJson, appendValue) and
  FirebaseAuth (serialiseMap, appendValue). Expanded each to the
  braced form.

All 44 identity-stack tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog shai-almog merged commit b088b73 into master May 25, 2026
28 checks passed
shai-almog added a commit that referenced this pull request May 25, 2026
Combine the two non-overlapping accept-list blocks (router & JS port
identifiers from this PR, WebAuthn / passkey terms from master's #5039)
into one section.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant