[Essentials] Passkeys (WebAuthn/FIDO2) Essentials API - #36837
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36837Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36837" |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
PureWeen
left a comment
There was a problem hiding this comment.
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 enteringTask.Run, crossing the window apartment boundary. ⚠️ Passkeys.windows.cs: modernresidentKey: "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.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
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/IPasskeysplus 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. |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 2 findings
See inline comments for details.
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
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 usePasskeys.Local.props(generated fromPasskeys.Local.in.propsand imported byEssentials.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
UserSecretsIdis set toessentials-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>
PureWeen
left a comment
There was a problem hiding this comment.
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 setsbPreferResidentKey. 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
left a comment
There was a problem hiding this comment.
Could you please check the suggestions?
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 810ccfa8-a9ee-4eb3-9b68-682bfa8fb99a
There was a problem hiding this comment.
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
UserSecretsIdappears 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 noPasskeyExceptiontype in the code). Either add the missing public exception type (and list it in allPublicAPI.Unshipped.txtvariants), or update the PR description/spec to match the implemented API surface.
This comment has been minimized.
This comment has been minimized.
kubaflo
left a comment
There was a problem hiding this comment.
Could you please resolve conflicts?
…-passkeys-server # Conflicts: # eng/Versions.props
This comment has been minimized.
This comment has been minimized.
AI Review Summary
🗂️ Review Sessions — click to expand🚦 Gate — Test Before & After FixGate Result: ✅ PASSEDPlatform: IOS · Base: net11.0 · Merge base: ✅ 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.
🔴 Without fix — 🧪 Passkeys_Tests: 🛠️ BUILD ERROR · 21sError-relevant lines (filtered from the build log): 🟢 With fix — 🧪 Passkeys_Tests: PASS ✅ · 8s(no coded error found; showing last 1200 chars) 🔴 Without fix — 📱 Passkeys_Windows_Tests (MapResidentKeyPreservesModernModes, MakeCredentialOptionsVersionMatchesCapabilities, ApiVersionOverrideCanOnlyLowerVersion): PASS ❌ · 347s(no coded error found; showing last 1200 chars) 🟢 With fix — 📱 Passkeys_Windows_Tests (MapResidentKeyPreservesModernModes, MakeCredentialOptionsVersionMatchesCapabilities, ApiVersionOverrideCanOnlyLowerVersion): PASS ✅ · 51s(no coded error found; showing last 1200 chars)
|
| 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.excludeCredentialsbut 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
ExcludedCredentialsmember 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 WebAuthnexcludeCredentials, 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 --requiredunavailable due GH auth. - Public REST result: PR head
53a582ehas 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-restorepassed.
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 == falseand 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); |
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; |
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.
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, andPasskeys.AssertAsync(optionsJson)→PasskeyAssertionResponse. The API drives the platform's native passkey UI (AppleASAuthorization…, 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 inPublicAPI.Unshipped.txtfor all TFMs, with unit tests inPasskeys_Tests.csand the full design indocs/specs/Passkeys.md.On Windows, an internal managed
WindowsWebAuthnlayer owns native buffers/cancellation and calls CsWin32-generated bindings for the in-boxwebauthn.dll.Microsoft.Windows.CsWin32is 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.WebAuthenticatornaming convention. It exposes only what the native sample exercises:MapIdentityApiunder/account(register,login?useCookies=true)/passkeys/list·/passkeys/register/begin·/passkeys/register/finish·/passkeys/login/begin·/passkeys/login/finish/.well-known/assetlinks.json(Android) and/.well-known/apple-app-site-association(Apple)/health, which reports the RP ID and configured Android/Apple trustThe ceremony logic uses the official ASP.NET Core Identity passkey implementation, including
PasskeySignInAsyncfor 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:
EnsureCreated()at startup — no file on disk, no EF migrations.IdentityUser/IdentityDbContextdirectly (no customApplicationUser/DbContext).Configure-Passkeys.ps1; nothing sensitive inappsettings.json.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/-NoAndroidexplicitly opt out.src/Essentials/samples/README-Passkeys.md— the single setup + usage guide (server/tunnel, then per-platform Apple/Android/Windows).Microsoft.Windows.SDK.BuildTools.WinApp, supplied transitively by MAUI Core on the rebasednet11.0branch.Verification
Passkeys_Tests: 15/15 passing, including resident-key precedence, timeout validation, extension JSON, and malformed Base64Url exception mapping.200→ pre-login/passkeys/list&/register/begin401→login?useCookies=true200→ authenticated list/begin endpoints200; both/.well-known/*docs and/healthserve200JSON.Relationship to the earlier PRs
This PR replaces the earlier stacked pair and both will be closed in its favour:
dotnet newBlazor Identity baseline" to diff a scaffolded server against. The server here is a hand-written minimal-API app instead, so that baseline no longer applies.