Skip to content

[Essentials] Passkeys (WebAuthn/FIDO2) Essentials API - #36837

Merged
kubaflo merged 74 commits into
net11.0from
mattleibow-minimal-passkeys-server
Aug 1, 2026
Merged

[Essentials] Passkeys (WebAuthn/FIDO2) Essentials API#36837
kubaflo merged 74 commits into
net11.0from
mattleibow-minimal-passkeys-server

Conversation

@mattleibow

@mattleibow mattleibow commented Jul 27, 2026

Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

What this is

The cross-platform Passkeys (WebAuthn/FIDO2) Essentials API — spec, Apple/Android/Windows implementation, public API, and unit tests — plus the MAUI Essentials sample (a new Passkeys page) and a tiny headless reference relying-party (RP) server the sample authenticates against.

This is now the primary PR for landing passkeys. It supersedes and will replace the earlier stacked PRs — see Relationship to the earlier PRs.

The Essentials API (src/Essentials/src/Passkeys)

Passkeys.IsSupported, Passkeys.CreateAsync(optionsJson)PasskeyCreationResponse, and Passkeys.AssertAsync(optionsJson)PasskeyAssertionResponse. The API drives the platform's native passkey UI (Apple ASAuthorization…, Android Credential Manager, Windows WebAuthn) and returns the raw WebAuthn JSON — it does no server verification itself; that's the relying party's job. New public API is tracked in PublicAPI.Unshipped.txt for all TFMs, with unit tests in Passkeys_Tests.cs and the full design in docs/specs/Passkeys.md.

On Windows, an internal managed WindowsWebAuthn layer owns native buffers/cancellation and calls CsWin32-generated bindings for the in-box webauthn.dll. Microsoft.Windows.CsWin32 is a private build-time source-generator dependency, not a runtime dependency for Essentials consumers.

The reference server (src/Essentials/samples/Samples.Server.Passkeys)

A deliberately tiny, throwaway dev tool — a headless minimal-API app (no web UI), 6 files, following the existing Sample.Server.WebAuthenticator naming convention. It exposes only what the native sample exercises:

  • username/password accounts via MapIdentityApi under /account (register, login?useCookies=true)
  • the passkey ceremony — /passkeys/list · /passkeys/register/begin · /passkeys/register/finish · /passkeys/login/begin · /passkeys/login/finish
  • the platform domain-association docs — /.well-known/assetlinks.json (Android) and /.well-known/apple-app-site-association (Apple)
  • /health, which reports the RP ID and configured Android/Apple trust

The ceremony logic uses the official ASP.NET Core Identity passkey implementation, including PasskeySignInAsync for policy checks, passkey-state persistence, and cookie sign-in. A green add + passkey sign-in therefore doubles as an interop conformance check that the Essentials API produced a valid, server-verifiable credential across Apple, Android, and Windows.

Deliberately minimal:

  • In-memory SQLite (real relational engine, RAM-backed via a keep-alive shared connection), schema created with EnsureCreated() at startup — no file on disk, no EF migrations.
  • Uses the framework's IdentityUser / IdentityDbContext directly (no custom ApplicationUser/DbContext).
  • All RP config (server domain / origin / Android + Apple association) comes from user-secrets, written by Configure-Passkeys.ps1; nothing sensitive in appsettings.json.

⚠️ Local dev tool: for convenience it doesn't require email confirmation, and its native /passkeys/* API authenticates the session with a cookie rather than a bearer token. Fine for a sample; not how a production native app should do it.

The sample + setup

  • src/Essentials/samples/Samples — the new Passkeys page (PasskeysPage + PasskeysViewModel): register/sign in with username+password, then create and sign in with a passkey. The server URL is baked in at build time by the setup script (no in-app configuration UI).
  • src/Essentials/samples/Configure-Passkeys.ps1 — provisions a persistent dev tunnel, writes server user-secrets and local app config, configures Android trust by default, and configures Apple signing/association on macOS. Apple is skipped automatically outside macOS; -NoApple/-NoAndroid explicitly opt out.
  • src/Essentials/samples/README-Passkeys.md — the single setup + usage guide (server/tunnel, then per-platform Apple/Android/Windows).
  • Packaged Windows samples can run from the CLI through Microsoft.Windows.SDK.BuildTools.WinApp, supplied transitively by MAUI Core on the rebased net11.0 branch.

Verification

  • Passkeys_Tests: 15/15 passing, including resident-key precedence, timeout validation, extension JSON, and malformed Base64Url exception mapping.
  • Builds pass for Windows (with AOT analyzer), Android API 37, iOS, and Mac Catalyst; the reference server builds with 0 warnings/errors.
  • Cookie-jar server smoke test: register 200 → pre-login /passkeys/list & /register/begin 401login?useCookies=true 200 → authenticated list/begin endpoints 200; both /.well-known/* docs and /health serve 200 JSON.
  • On-device ceremonies completed end-to-end (create account → create passkey → sign in with passkey) over a public dev tunnel on iOS Simulator, Mac Catalyst, and Windows 11 / Windows Hello.

Relationship to the earlier PRs

This PR replaces the earlier stacked pair and both will be closed in its favour:

@mattleibow
mattleibow temporarily deployed to copilot-pat-pool July 27, 2026 18:55 — with GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36837

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36837"

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

@mattleibow
mattleibow temporarily deployed to copilot-pat-pool July 27, 2026 18:56 — with GitHub Actions Inactive
@mattleibow
mattleibow temporarily deployed to copilot-pat-pool July 27, 2026 18:57 — with GitHub Actions Inactive
@mattleibow
mattleibow temporarily deployed to copilot-pat-pool July 27, 2026 19:00 — with GitHub Actions Inactive
@mattleibow
mattleibow temporarily deployed to copilot-pat-pool July 27, 2026 19:01 — with GitHub Actions Inactive
@mattleibow
mattleibow temporarily deployed to copilot-pat-pool July 27, 2026 19:02 — with GitHub Actions Inactive
@mattleibow mattleibow changed the title Passkeys (WebAuthn/FIDO2) Essentials API — final, with minimal sample server Passkeys (WebAuthn/FIDO2) Essentials API + minimal reference server Jul 27, 2026
@mattleibow mattleibow changed the title Passkeys (WebAuthn/FIDO2) Essentials API + minimal reference server [Essentials] Passkeys (WebAuthn/FIDO2) Essentials API Jul 28, 2026

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial code review

Found 2 errors, 1 warning, and 1 hardening suggestion.

  • PasskeyEndpoints.cs: the hand-composed passkey sign-in path bypasses ASP.NET Core Identity pre-sign-in/lockout checks and signs in even if passkey-state persistence fails.
  • Passkeys.windows.cs: the active WinUI window handle is resolved after entering Task.Run, crossing the window apartment boundary.
  • ⚠️ Passkeys.windows.cs: modern residentKey: "required" is ignored, so Windows can create a non-discoverable credential that cannot satisfy username-less sign-in.
  • 💡 Program.cs: forwarded headers are trusted from every direct caller, which can affect cookie transport security outside the intended tunnel path.

PR metadata: The title accurately describes the scope. The description is strong overall, but its claim that the server uses the official Identity implementation with "nothing hand-rolled" does not match the current /login/finish composition; using PasskeySignInAsync would make that claim accurate.

What looks right: Public API baseline entries align across TFMs, and the shared response wrappers have focused unit coverage.

Test coverage: Partial. Shared JSON response behavior is covered, but the native Windows/Android/iOS paths and sample-server endpoint behavior are not automated here.

Prior review status: No earlier substantive review findings or unresolved threads were present.

Methodology: 3 independent reviewers with adversarial consensus + repo domain specialist.

Comment thread src/Essentials/samples/Samples.Server.Passkeys/PasskeyEndpoints.cs Outdated
Comment thread src/Essentials/src/Passkeys/Passkeys.windows.cs Outdated
Comment thread src/Essentials/src/Passkeys/Passkeys.windows.cs Outdated
Comment thread src/Essentials/samples/Samples.Server.Passkeys/Program.cs Outdated
@mattleibow

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new cross-platform Passkeys (WebAuthn/FIDO2) API to Microsoft.Maui.Essentials (plus platform implementations, unit tests, a runnable Essentials sample page, and a minimal reference RP server) so MAUI apps can drive native passkey UX and round-trip standard WebAuthn JSON to a server for verification.

Changes:

  • Introduces Microsoft.Maui.Authentication.Passkeys / IPasskeys plus option/response wrappers and per-platform implementations (Android Credential Manager, Apple AuthenticationServices, Windows WebAuthn).
  • Adds supporting WebAuthn JSON models/helpers, public API tracking entries, and unit tests.
  • Adds Essentials sample “Passkeys” page, a minimal Identity-based RP server, setup script/docs, and wires projects into solution/slnf filters.

Reviewed changes

Copilot reviewed 43 out of 43 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/Essentials/test/UnitTests/Passkeys_Tests.cs Adds unit coverage for option/response wrappers and WebAuthn helper logic.
src/Essentials/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt Tracks new Passkeys public API surface for netstandard.
src/Essentials/src/PublicAPI/net/PublicAPI.Unshipped.txt Tracks new Passkeys public API surface for net.
src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt Tracks new Passkeys public API surface for Windows TFM.
src/Essentials/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt Tracks new Passkeys public API surface for Tizen TFM.
src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt Tracks new Passkeys public API surface for Mac Catalyst TFM.
src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt Tracks new Passkeys public API surface for iOS TFM.
src/Essentials/src/PublicAPI/net-android/PublicAPI.Unshipped.txt Tracks new Passkeys public API surface for Android TFM.
src/Essentials/src/Passkeys/WindowsWebAuthn.windows.cs Implements Windows WebAuthn P/Invoke ceremony execution and native buffer management.
src/Essentials/src/Passkeys/Passkeys.windows.cs Windows IPasskeys implementation, JSON mapping, cancellation, and HWND resolution.
src/Essentials/src/Passkeys/Passkeys.shared.cs Defines public API (IPasskeys, options/responses, and static Passkeys facade).
src/Essentials/src/Passkeys/Passkeys.netstandard.tvos.tizen.cs Not-supported stub implementation for unsupported TFMs.
src/Essentials/src/Passkeys/Passkeys.ios.cs Apple (iOS/Mac Catalyst) implementation using AuthenticationServices + TCS bridge.
src/Essentials/src/Passkeys/Passkeys.android.cs Android implementation using AndroidX Credential Manager.
src/Essentials/src/Passkeys/PasskeyJson.shared.cs Shared WebAuthn JSON models + source-gen context + helper parsing/serialization.
src/Essentials/src/NativeMethods.txt CsWin32 native method list for Windows WebAuthn interop generation.
src/Essentials/src/NativeMethods.json CsWin32 configuration for generated interop.
src/Essentials/src/Essentials.csproj Adds Windows/Android dependencies needed for passkeys (CsWin32 + AndroidX Credentials).
src/Essentials/samples/Samples/ViewModel/PasskeysViewModel.cs Adds sample page VM (account flow, passkey create/assert, cookie-based server calls).
src/Essentials/samples/Samples/ViewModel/HomeViewModel.cs Registers the Passkeys sample page in the sample gallery.
src/Essentials/samples/Samples/View/PasskeysPage.xaml.cs Adds Passkeys page code-behind.
src/Essentials/samples/Samples/View/PasskeysPage.xaml Adds Passkeys sample UI for sign-in/register + passkey flows.
src/Essentials/samples/Samples/Passkeys.Local.in.props Template for local (git-ignored) Passkeys sample configuration.
src/Essentials/samples/Samples/Essentials.Sample.csproj Adds Windows manifest/buildtools + imports local passkey props when present.
src/Essentials/samples/Samples/.gitignore Ignores locally generated passkey config and entitlements.
src/Essentials/samples/Samples.Server.Passkeys/Properties/launchSettings.json Adds launch profiles for the reference RP server.
src/Essentials/samples/Samples.Server.Passkeys/Program.cs Configures Identity + in-memory SQLite + forwarded headers + passkey options and routes.
src/Essentials/samples/Samples.Server.Passkeys/PasskeyEndpoints.cs Implements passkey ceremony endpoints and well-known association documents.
src/Essentials/samples/Samples.Server.Passkeys/IdentityNoOpEmailSender.cs No-op email sender required by Identity registration flow for this headless sample.
src/Essentials/samples/Samples.Server.Passkeys/Essentials.Samples.Server.Passkeys.csproj New minimal ASP.NET Core server project for passkeys sample.
src/Essentials/samples/Samples.Server.Passkeys/appsettings.json Server logging/host defaults.
src/Essentials/samples/README.md Adds top-level samples README and links to passkeys scenario.
src/Essentials/samples/README-Passkeys.md Adds detailed end-to-end passkeys sample setup/usage guide.
src/Essentials/samples/Configure-Passkeys.ps1 Adds automation script to provision dev tunnel + user-secrets + local app props/entitlements.
Microsoft.Maui.sln Adds the new passkeys server project to the solution.
Microsoft.Maui-windows.slnf Adds the passkeys server project to the Windows solution filter.
Microsoft.Maui-vscode.sln Adds the passkeys server project to the VS Code solution.
Microsoft.Maui-mac.slnf Adds the passkeys server project to the mac solution filter.
Microsoft.Maui-dev.sln Adds the passkeys server project to the dev solution.
eng/Versions.props Adds package version entries required by new dependencies.
eng/NuGetVersions.targets Centralizes versions for new packages used by the PR.
eng/AndroidX.targets Pins Xamarin.AndroidX.Credentials version.
docs/specs/Passkeys.md Adds full Passkeys API design spec and rationale.

Comment thread src/Essentials/src/Passkeys/PasskeyJson.shared.cs
Comment thread src/Essentials/samples/Samples.Server.Passkeys/PasskeyEndpoints.cs Outdated
Comment thread src/Essentials/samples/Samples/Passkeys.Local.in.props
Comment thread src/Essentials/samples/Samples/.gitignore Outdated
Comment thread src/Essentials/samples/Configure-Passkeys.ps1 Outdated
@mattleibow mattleibow added this to the .NET 11.0-preview7 milestone Jul 28, 2026
@kubaflo

This comment has been minimized.

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 2 findings

See inline comments for details.

Comment thread src/Essentials/src/Passkeys/Passkeys.ios.cs
Comment thread src/Essentials/src/Passkeys/Passkeys.ios.cs
@MauiBot MauiBot added s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Jul 28, 2026
MauiBot

This comment was marked as outdated.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 43 out of 43 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/Essentials/samples/README-Passkeys.md:145

  • README refers to Signing.local.props, but the setup script/app project use Passkeys.Local.props (generated from Passkeys.Local.in.props and imported by Essentials.Sample.csproj). Updating this avoids confusion when following the Mac Catalyst signing instructions.
   - **iOS Simulator** (unsigned): `dotnet run --project Samples/Essentials.Sample.csproj -f net11.0-ios`
   - **Mac Catalyst** (signed via `Signing.local.props`): `dotnet run --project Samples/Essentials.Sample.csproj -f net11.0-maccatalyst`
   - **Real iOS device**: signed the same way, deploy from your IDE.

src/Essentials/samples/Samples.Server.Passkeys/Essentials.Samples.Server.Passkeys.csproj:9

  • UserSecretsId is set to essentials-sample-webserver, which is generic and could easily collide with other samples/servers, causing user-secrets to be shared unintentionally. Consider using a unique ID for this specific project (e.g. essentials-samples-server-passkeys).
    <ImplicitUsings>enable</ImplicitUsings>
    <RootNamespace>Samples.Server.Passkeys</RootNamespace>
    <UserSecretsId>essentials-sample-webserver</UserSecretsId>
  </PropertyGroup>

Comment thread src/Essentials/src/Passkeys/PasskeyJson.shared.cs Outdated

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial code review — round 2

Found 1 error on the current GitHub head.

  • ❌ Windows collapses residentKey: "preferred" into the same native request as "discouraged"; the generated binding never sets bPreferResidentKey. This is an incomplete/self-introduced follow-up to the prior resident-key fix.

Prior review status: The earlier Identity policy/persistence, HWND-threading, residentKey: "required", and forwarded-header findings are addressed. Registration-finish principal binding, malformed Base64Url mapping, and pre-canceled iOS presentation are also fixed on the current head; stale local-checkout findings were discarded.

Test coverage: Improved shared-helper coverage now exercises required/legacy precedence and malformed Base64Url mapping. However, the resident-key test currently codifies "preferred" => false, and no Windows-targeted test verifies native make-credential option fields/versioning.

PR metadata: The title and updated description match the implementation; no metadata change is needed.

Methodology: 3 independent reviewers with adversarial consensus + repo domain specialist.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please check the suggestions?

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 810ccfa8-a9ee-4eb3-9b68-682bfa8fb99a
Copilot AI review requested due to automatic review settings July 30, 2026 20:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 44 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/Essentials/samples/Samples.Server.Passkeys/Essentials.Samples.Server.Passkeys.csproj:8

  • UserSecretsId appears to be copied from an unrelated sample (essentials-sample-webserver). This will cause user-secrets for the Passkeys server to collide with any other sample that uses that same ID, and makes it harder to keep configs isolated per sample. Use a Passkeys-specific ID (e.g., essentials-sample-passkeys).
    <UserSecretsId>essentials-sample-webserver</UserSecretsId>

src/Essentials/src/PublicAPI/net/PublicAPI.Unshipped.txt:27

  • The PR description mentions a public PasskeyException, but the current public API list for this TFM does not include it (and there is no PasskeyException type in the code). Either add the missing public exception type (and list it in all PublicAPI.Unshipped.txt variants), or update the PR description/spec to match the implemented API surface.

@mattleibow

This comment has been minimized.

@github-actions github-actions Bot added s/agent-review-in-progress AI review is currently running for this PR and removed s/agent-review-in-progress AI review is currently running for this PR labels Jul 30, 2026

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please resolve conflicts?

…-passkeys-server

# Conflicts:
#	eng/Versions.props
Copilot AI review requested due to automatic review settings July 31, 2026 17:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 44 changed files in this pull request and generated no new comments.

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Aug 1, 2026
@MauiBot MauiBot added the s/agent-changes-requested AI agent recommends changes - found a better alternative or issues label Aug 1, 2026
@MauiBot

MauiBot commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

AI Review Summary

@mattleibow — new AI review results are available based on this last commit: cae11ce.

Gate Passed Confidence Medium Platform iOS


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ✅ PASSED

Platform: IOS · Base: net11.0 · Merge base: 7bb23673

Verified (new API / feature) — this PR adds new API and a test that references it in the same project, so reverting the fix un-compiles the test: there is no valid "fails without the fix" baseline to establish (a compile-coupled baseline). The gate instead verified the fix by a clean build + pass with the fix, so this is a real PASS rather than a non-committal INCONCLUSIVE.

Test Without Fix (expect FAIL) With Fix (expect PASS)
🧪 Passkeys_Tests Passkeys_Tests 🛠️ BUILD ERROR ✅ PASS — 8s
📱 Passkeys_Windows_Tests (MapResidentKeyPreservesModernModes, MakeCredentialOptionsVersionMatchesCapabilities, ApiVersionOverrideCanOnlyLowerVersion) Category=Passkeys ❌ PASS — 347s ✅ PASS — 51s
🔴 Without fix — 🧪 Passkeys_Tests: 🛠️ BUILD ERROR · 21s

Error-relevant lines (filtered from the build log):

/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Passkeys_Tests.cs(13,22): error CS0246: The type or namespace name 'PasskeyCreationResponse' could not be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Essentials.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Passkeys_Tests.cs(22,54): error CS0246: The type or namespace name 'PasskeyCreationResponse' could not be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Essentials.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Passkeys_Tests.cs(29,22): error CS0246: The type or namespace name 'PasskeyAssertionResponse' could not be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Essentials.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Passkeys_Tests.cs(38,22): error CS0246: The type or namespace name 'PasskeyAssertionResponse' could not be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Essentials.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Passkeys_Tests.cs(46,54): error CS0246: The type or namespace name 'PasskeyAssertionResponse' could not be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Essentials.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Passkeys_Tests.cs(53,26): error CS0246: The type or namespace name 'PasskeyCreationOptions' could not be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Essentials.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Passkeys_Tests.cs(54,26): error CS0246: The type or namespace name 'PasskeyRequestOptions' could not be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Essentials.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Passkeys_Tests.cs(60,52): error CS0103: The name 'WebAuthn' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Essentials.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Passkeys_Tests.cs(61,24): error CS0103: The name 'WebAuthn' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Essentials.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Passkeys_Tests.cs(67,58): error CS0103: The name 'WebAuthn' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Essentials.UnitTests.csproj]
🟢 With fix — 🧪 Passkeys_Tests: PASS ✅ · 8s

(no coded error found; showing last 1200 chars)

 (.NETCoreApp,Version=v11.0)
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 11.0.0-rc.1.26379.102)
[xUnit.net 00:00:00.04]   Discovering: Microsoft.Maui.Essentials.UnitTests
[xUnit.net 00:00:00.11]   Discovered:  Microsoft.Maui.Essentials.UnitTests
[xUnit.net 00:00:00.11]   Starting:    Microsoft.Maui.Essentials.UnitTests
[xUnit.net 00:00:00.16]   Finished:    Microsoft.Maui.Essentials.UnitTests
  Passed Tests.Passkeys_Tests.AssertionResponse_Throws_When_Id_Missing [12 ms]
  Passed Tests.Passkeys_Tests.AssertionResponse_UserHandle_Null_When_Absent [< 1 ms]
  Passed Tests.Passkeys_Tests.CreationResponse_Throws_When_Id_Missing [2 ms]
  Passed Tests.Passkeys_Tests.GetTimeout_Rejects_Negative_Values [2 ms]
  Passed Tests.Passkeys_Tests.DecodeRequired_Rejects_Invalid_Base64Url_As_ArgumentException [1 ms]
  Passed Tests.Passkeys_Tests.CreationResponse_Exposes_Id [< 1 ms]
  Passed Tests.Passkeys_Tests.AssertionResponse_Exposes_Id_And_UserHandle [< 1 ms]
  Passed Tests.Passkeys_Tests.Options_ToString_Returns_Raw_Json [< 1 ms]
Test Run Successful.
Total tests: 8
     Passed: 8
 Total time: 0.4408 Seconds
🔴 Without fix — 📱 Passkeys_Windows_Tests (MapResidentKeyPreservesModernModes, MakeCredentialOptionsVersionMatchesCapabilities, ApiVersionOverrideCanOnlyLowerVersion): PASS ❌ · 347s

(no coded error found; showing last 1200 chars)

"HF9M9H2H36-1",
        "exitCode": 0,
        "exitCodeName": "SUCCESS",
        "platform": "apple",
        "device": "iPhone 11 Pro",
        "deviceOsVersion": "26.5",
        "files": [
          {
            "name": "test-ios-simulator-64_26.5-B90C6FF4-2D3A-4FD6-9EBA-5507A3B6957D.log",
            "type": "executionlog"
          },
          {
            "name": "list-ios-simulator-64_26.5-20260801_101226.log",
            "type": "devicelist"
          },
          {
            "name": "test-ios-simulator-64_26.5-20260801_101234.log",
            "type": "testlog"
          },
          {
            "name": "iPhone 11 Pro.log",
            "type": "systemlog"
          },
          {
            "name": "Microsoft.Maui.Essentials.DeviceTests.log",
            "type": "systemlog"
          },
          {
            "name": "com.microsoft.maui.essentials.devicetests.log",
            "type": "applicationlog"
          },
          {
            "name": "xunit-test-ios-simulator-64_26.5-20260801_101234.xml",
            "type": "xmllog"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
XHarness exit code: 0
  Passed: 0
  Failed: 0
  Tests completed successfully
🟢 With fix — 📱 Passkeys_Windows_Tests (MapResidentKeyPreservesModernModes, MakeCredentialOptionsVersionMatchesCapabilities, ApiVersionOverrideCanOnlyLowerVersion): PASS ✅ · 51s

(no coded error found; showing last 1200 chars)

F9M9H2H36-1",
        "exitCode": 0,
        "exitCodeName": "SUCCESS",
        "platform": "apple",
        "device": "iPhone 11 Pro",
        "deviceOsVersion": "26.5",
        "files": [
          {
            "name": "test-ios-simulator-64_26.5-B90C6FF4-2D3A-4FD6-9EBA-5507A3B6957D.log",
            "type": "executionlog"
          },
          {
            "name": "list-ios-simulator-64_26.5-20260801_101541.log",
            "type": "devicelist"
          },
          {
            "name": "test-ios-simulator-64_26.5-20260801_101544.log",
            "type": "testlog"
          },
          {
            "name": "iPhone 11 Pro.log",
            "type": "systemlog"
          },
          {
            "name": "Microsoft.Maui.Essentials.DeviceTests.log",
            "type": "systemlog"
          },
          {
            "name": "com.microsoft.maui.essentials.devicetests.log",
            "type": "applicationlog"
          },
          {
            "name": "xunit-test-ios-simulator-64_26.5-20260801_101544.xml",
            "type": "xmllog"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
XHarness exit code: 0
  Passed: 729
  Failed: 0
  Tests completed successfully

⚠️ Failure Details

  • 🛠️ Passkeys_Tests without fix: build failed before tests could run
    • /Users/cloudtest/vss/_work/1/s/src/Essentials/test/UnitTests/Passkeys_Tests.cs(13,22): error CS0246: The type or namespace name 'PasskeyCreationResponse' could not be found (are you missing a using di...
  • Passkeys_Windows_Tests (MapResidentKeyPreservesModernModes, MakeCredentialOptionsVersionMatchesCapabilities, ApiVersionOverrideCanOnlyLowerVersion) PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (18 files)
  • Microsoft.Maui-dev.sln
  • Microsoft.Maui-mac.slnf
  • Microsoft.Maui-vscode.sln
  • Microsoft.Maui-windows.slnf
  • Microsoft.Maui.sln
  • eng/AndroidX.targets
  • eng/NuGetVersions.targets
  • eng/Versions.props
  • src/Essentials/samples/Samples/Essentials.Sample.csproj
  • src/Essentials/samples/Samples/ViewModel/HomeViewModel.cs
  • src/Essentials/src/Essentials.csproj
  • src/Essentials/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
  • src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
  • src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
  • src/Essentials/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
  • src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
  • src/Essentials/src/PublicAPI/net/PublicAPI.Unshipped.txt
  • src/Essentials/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt

New files (not reverted):

  • src/Essentials/samples/Configure-Passkeys.ps1
  • src/Essentials/samples/Samples.Server.Passkeys/Essentials.Samples.Server.Passkeys.csproj
  • src/Essentials/samples/Samples.Server.Passkeys/IdentityNoOpEmailSender.cs
  • src/Essentials/samples/Samples.Server.Passkeys/PasskeyEndpoints.cs
  • src/Essentials/samples/Samples.Server.Passkeys/Program.cs
  • src/Essentials/samples/Samples.Server.Passkeys/Properties/launchSettings.json
  • src/Essentials/samples/Samples.Server.Passkeys/appsettings.json
  • src/Essentials/samples/Samples/.gitignore
  • src/Essentials/samples/Samples/Passkeys.Local.in.props
  • src/Essentials/samples/Samples/View/PasskeysPage.xaml
  • src/Essentials/samples/Samples/View/PasskeysPage.xaml.cs
  • src/Essentials/samples/Samples/ViewModel/PasskeysViewModel.cs
  • src/Essentials/src/NativeMethods.json
  • src/Essentials/src/NativeMethods.txt
  • src/Essentials/src/Passkeys/PasskeyJson.shared.cs
  • src/Essentials/src/Passkeys/Passkeys.android.cs
  • src/Essentials/src/Passkeys/Passkeys.ios.cs
  • src/Essentials/src/Passkeys/Passkeys.netstandard.tvos.tizen.cs
  • src/Essentials/src/Passkeys/Passkeys.shared.cs
  • src/Essentials/src/Passkeys/Passkeys.windows.cs
  • src/Essentials/src/Passkeys/WindowsWebAuthn.windows.cs

📱 UI Tests — Essentials

Detected UI test categories: Essentials

⏭️ Deep UI tests — 0 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above). 1 category reported 0 tests.

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
Essentials 0 tests
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

📋 Pre-Flight — Context & Validation

Issue: #36837 - [Essentials] Passkeys (WebAuthn/FIDO2) Essentials API
PR: #36837 - [Essentials] Passkeys (WebAuthn/FIDO2) Essentials API
Platforms Affected: Android, iOS, Mac Catalyst, Windows; try-fix platform: iOS
Files Changed: 37 implementation/sample/docs/config, 7 public API/test

Key Findings

  • PR adds cross-platform native Passkeys/WebAuthn Essentials API, server/sample support, docs, public API entries, and Passkeys unit tests.
  • The iOS path parses creation.excludeCredentials but does not enforce it, unlike Windows and Android; this can permit duplicate passkey creation when the RP explicitly asked the authenticator to exclude an existing credential.
  • Public iOS bindings do not expose a usable ExcludedCredentials member on the concrete native-app registration request or public protocol interface, despite related metadata strings existing for other AuthenticationServices surfaces.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: medium
Errors: 1 | Warnings: 0 | Suggestions: 0

Key code review findings:

  • src/Essentials/src/Passkeys/Passkeys.ios.cs:42-53 — iOS registration ignores WebAuthn excludeCredentials, creating cross-platform correctness divergence for duplicate credential prevention.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #36837 Documents/ignores iOS native-app excludeCredentials limitation ✅ PASSED (Gate) src/Essentials/src/Passkeys/Passkeys.ios.cs Original PR; gate already completed outside this run.

🔬 Code Review — Deep Analysis

Code Review — PR #36837

Independent Assessment

What this changes: Adds a new Essentials Passkeys/WebAuthn API with Android, Apple, Windows, unsupported-platform implementations, public API entries, unit tests, docs, and a sample/reference RP server.
Inferred motivation: Provide cross-platform native passkey registration/assertion while leaving challenge generation and verification to the relying-party server.

Reconciliation with PR Narrative

Author claims: PR adds the Passkeys Essentials API, WebAuthn JSON pass-through, native platform implementations, samples, and tests.
Agreement/disagreement: The implementation matches the stated scope. One Apple behavior remains inconsistent with the cross-platform contract.

Prior Review Reconciliation

Prior ❌ Error Finding Source Status Evidence
Identity sign-in bypass/persistence failure in sample server PureWeen ✅ Fixed PasskeyEndpoints.cs:137-159 now uses PasskeySignInAsync and checks SignInResult.
Windows HWND resolved inside Task.Run PureWeen ✅ Fixed Passkeys.windows.cs:28-29 captures HWND before entering the worker.
Windows residentKey required/preferred not preserved PureWeen ✅ Fixed Passkeys.windows.cs:254-260; WindowsWebAuthn.windows.cs:146-158.
iOS ignores excludeCredentials MauiBot [major] ❌ Unresolved Passkeys.ios.cs:42-43 says it is not mapped; no registration exclusion is applied.
iOS pre-canceled request still presents sheet MauiBot [major] ✅ Fixed Passkeys.ios.cs:176-208 re-checks cancellation before PerformRequests.

Blast Radius Assessment

  • Runs for all instances: No — only callers using Passkeys.
  • Startup impact: No.
  • Static/shared state: Yes — Passkeys.Default, but no new startup side effect observed.

External Output Contract

Consumer token/pattern Producer location Producer emission condition Consumer assumption Ordinary negative case Downstream effect
SHA256: keytool -list -v in Configure-Passkeys.ps1 Keystore certificate details include SHA-256 fingerprint First matching line is app signing cert fingerprint Missing/wrong keystore Script fails before writing Android trust config
OU = [A-Z0-9]{10} `security find-certificate openssl x509` Apple cert subject includes Team ID OU OU is Apple Team ID No cert / unparsable subject
provisioning-profile XML keys security cms -D Installed profile contains entitlements/app id/name Matching profile can sign associated domains No matching profile Apple device signing skipped/warned

CI Status

  • Required-check result: gh pr checks --required unavailable due GH auth.
  • Public REST result: PR head 53a582e has 32 check runs; maui-pr, license/cla, build/integration/Helix checks completed success; one non-gating bump check skipped.
  • Classification: no failing check-runs observed.
  • Action taken: targeted local dotnet test src/Essentials/test/UnitTests/Essentials.UnitTests.csproj --filter Passkeys --no-restore passed.

Findings

❌ Error — iOS registration ignores excludeCredentials

src/Essentials/src/Passkeys/Passkeys.ios.cs:42-53

The iOS registration path parses creation options but never maps creation.ExcludeCredentials onto the native registration request. Android forwards the full WebAuthn JSON, and Windows maps ExcludeCredentials; iOS therefore allows a normal duplicate-registration case that the RP explicitly asked the authenticator to reject. The existing MapAllowedCredentials helper already creates the descriptor type needed for assertions, so registration should similarly apply excluded credentials where the platform API supports it.

Failure-Mode Probing

  • Existing passkey, server sends excludeCredentials: Android/Windows can reject duplicate registration; iOS ignores the exclusion and may create or offer a duplicate credential.
  • Cancellation before iOS presentation: now checked before PerformRequests; no unresolved issue found.
  • Windows call from UI: HWND is captured before Task.Run; no unresolved threading issue found.
  • Unsupported platforms: return IsSupported == false and throw not-supported.

Verdict: NEEDS_CHANGES

Confidence: medium
Summary: The core API shape is coherent and unit tests pass, but an unresolved prior major finding remains: Apple registration does not honor WebAuthn excludeCredentials, causing cross-platform correctness divergence for duplicate passkey prevention.


🛠️ Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix Directly set request.ExcludedCredentials on the concrete iOS registration request. ❌ Fail 1 file net11.0-ios compile failed: concrete request has no public ExcludedCredentials property.
2 try-fix Cast to IASAuthorizationPublicKeyCredentialRegistrationRequest and set ExcludedCredentials behind a selector guard. ❌ Fail 1 file net11.0-ios compile failed: public protocol interface also has no ExcludedCredentials property.
3 try-fix Fail fast with FeatureNotSupportedException when iOS creation options include non-empty excludeCredentials. ✅ Pass (targeted compile/unit) 1 file Builds iOS/MacCatalyst and unit tests pass, but behavior can over-block registrations for credentials that exist only on other devices.
4 try-fix Best-effort immediate assertion preflight: block only when iOS can return a matching local excluded credential. ✅ Pass (targeted compile/unit); ⚠️ device behavior unvalidated 1 file Avoids private APIs and blanket over-blocking, but needs real iOS validation for UI/cancellation semantics.
PR PR #36837 Ignore iOS excludeCredentials with explanatory comment. ✅ PASSED (Gate) 1 file Original PR; gate already completed outside this run.

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 1 Yes Try native ExcludedCredentials mapping on the concrete registration request.
maui-expert-reviewer 2 Yes After CS1061 on concrete request, try the AuthenticationServices protocol/interface with selector guard.
maui-expert-reviewer 3 Yes After protocol compile failure, use an honest fail-fast unsupported gate for non-empty excludeCredentials.
maui-expert-reviewer 4 Yes After candidate 3 over-blocking concern, try best-effort public assertion preflight using AllowedCredentials and ImmediatelyAvailableCredentials.
maui-expert-reviewer 5 No Remaining options require private Objective-C messaging/reflection or real iOS device ceremony validation; no additional compile-safe approach is demonstrably better from static/build evidence alone.

Narrative

try-fix-1 — Direct native mapping

Candidate 1 attempted the most direct correction: reuse descriptor mapping and set request.ExcludedCredentials on ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest. This would have been the cleanest platform mapping if the binding exposed the member. It failed to compile for net11.0-ios with CS1061, while existing Passkeys unit tests still passed. The failure proved that metadata strings alone are insufficient; the concrete native-app request type does not expose a usable managed property.

try-fix-2 — Protocol/interface mapping

Candidate 2 tried the next plausible binding surface: cast the request to IASAuthorizationPublicKeyCredentialRegistrationRequest, guard setExcludedCredentials: with RespondsToSelector, then assign through the protocol. It also failed to compile with CS1061 because the public protocol interface lacks ExcludedCredentials. This eliminated supported native mapping through the managed public binding.

try-fix-3 — Strict unsupported gate

Candidate 3 accepted the native API gap and failed fast when excludeCredentials maps to at least one descriptor. This passed targeted iOS and MacCatalyst compilation and Passkeys unit tests. It is safer than silent ignore for duplicate prevention, but may over-block valid registrations when relying parties include credentials that are not present on the local device.

try-fix-4 — Best-effort assertion preflight

Candidate 4 used only public APIs already present in the file: issue an immediate assertion request with AllowedCredentials set to the excluded descriptors, then block registration only if the OS returns a matching credential ID. It passed targeted iOS and MacCatalyst compilation and Passkeys unit tests. It is the least restrictive compile-valid alternative, but cannot be called demonstrably better without on-device iOS validation because the preflight may show UI and may not distinguish no-credential from user cancellation cleanly.

Exhausted: Yes
Selected Fix: Candidate #4 as the best alternative candidate to take to device validation; Candidate #3 as the safest compile-valid fallback if product owners prefer explicit unsupported failure over possible preflight UX risk. No candidate was proven fully better than the PR fix under the available non-device test evidence.


🏁 Report — Final Recommendation

Comparative Report — PR #36837

Candidates compared

Rank Candidate Regression/targeted test result Assessment
1 pr-plus-reviewer ✅ Pass — PR gate already passed for iOS; sandboxed Passkeys unit tests passed 8/8 after reviewer patch Best overall. Keeps the submitted PR's broad implementation and documented iOS platform limitation, and applies the expert reviewer's low-risk Android pre-cancellation fix.
2 pr ✅ Pass — supplied gate passed Good baseline. The PR is coherent and validated by the gate, but the expert reviewer found an actionable Android cancellation issue.
3 try-fix-4 ✅ Pass targeted iOS/MacCatalyst compile + Passkeys unit tests; ⚠️ device behavior unvalidated Best iOS alternative if product owners require best-effort excludeCredentials handling, but it introduces an assertion preflight that may show UI and cannot cleanly prove no-credential vs cancellation semantics without real iOS ceremony validation.
4 try-fix-3 ✅ Pass targeted iOS/MacCatalyst compile + Passkeys unit tests Safer than silently ignoring excludeCredentials, but too conservative: it can reject legitimate registrations whenever the RP sends excluded credentials that are not present on the local iOS device.
5 try-fix-1 ❌ Failed iOS compile Direct request.ExcludedCredentials mapping would have been clean, but the concrete native-app registration request binding exposes no such public member. Failed candidates rank below all passing candidates.
6 try-fix-2 ❌ Failed iOS compile Protocol/interface mapping with selector guard also failed because IASAuthorizationPublicKeyCredentialRegistrationRequest exposes no public ExcludedCredentials member. Failed candidates rank below all passing candidates.

Key comparison points

try-fix-1 and try-fix-2 are eliminated because they fail compilation. Per the ranking rule, they cannot outrank any candidate that passed regression/targeted validation.

try-fix-3 and try-fix-4 are compile-valid but not clear winners over the PR. Candidate 3 changes the contract by failing every iOS registration with a non-empty excludeCredentials list, including ordinary valid cases where the credential exists only on another device. Candidate 4 is less restrictive, but it relies on an immediate assertion preflight whose user-interface and cancellation/no-credential behavior remains unvalidated on device.

The raw pr candidate passes the supplied gate and reflects a practical platform limitation in Apple's native-app registration API surface. However, the expert reviewer identified a separate Android cancellation issue that is concrete, easy to fix, and consistent with existing iOS/Windows cancellation patterns.

Winner

Winner: pr-plus-reviewer

pr-plus-reviewer is the single best candidate because it keeps the PR's validated implementation and avoids the unproven iOS alternatives, while applying the expert reviewer's actionable Android cancellation fix. It improves correctness without changing the public API or introducing new ceremony behavior risk.


🧭 Next Steps — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Aug 1, 2026
@kubaflo
kubaflo merged commit 2e4da84 into net11.0 Aug 1, 2026
32 checks passed
@kubaflo
kubaflo deleted the mattleibow-minimal-passkeys-server branch August 1, 2026 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-essentials-webauth platform/android platform/ios platform/macos macOS / Mac Catalyst platform/windows s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants