Skip to content

Expand MAUI Identity sample with REST account parity - #666

Draft
mattleibow wants to merge 14 commits into
dotnet:mainfrom
mattleibow:mattleibow-maui-blazor-identity
Draft

mattleibow wants to merge 14 commits into
dotnet:mainfrom
mattleibow:mattleibow-maui-blazor-identity

Conversation

@mattleibow

Copy link
Copy Markdown
Member

Summary

This draft explores what a fully featured, trusted first-party .NET MAUI client can do with ASP.NET Core Identity's in-box REST endpoints and opaque bearer tokens, without introducing OAuth/OIDC, JWTs, OpenIddict, or another third-party authorization server.

The sample keeps the stock MapIdentityApi<ApplicationUser>() surface intact under /identity, adds enhanced replacement contracts under /identity-overrides, and adds only currently missing account-management routes under /identity. The additional endpoint implementation lives in a framework-reference-only class library so it can serve as concrete input for potential ASP.NET Core Identity improvements.

How it works

Server endpoint composition

var identity = app.MapGroup("/identity");
identity.MapIdentityApi<ApplicationUser>();
identity.MapNewIdentityApi<ApplicationUser>();

app.MapGroup("/identity-overrides")
    .MapOverrideIdentityApi<ApplicationUser>();
  • Stock /identity routes remain framework-owned and directly comparable.
  • MapOverrideIdentityApi<TUser> provides richer contracts where an existing route lacks information needed by a native client.
  • MapNewIdentityApi<TUser> adds account capabilities for which MapIdentityApi currently has no route.
  • Native REST and application APIs explicitly require IdentityConstants.BearerScheme; normal Identity cookies continue to serve hosted Razor account pages.

The tokens remain ASP.NET Core Identity Data Protection bearer tickets. They are opaque, not JWTs, and this is deliberately a first-party application pattern rather than an OAuth authorization server.

MAUI client

The MAUI client now uses typed REST calls for:

  • registration, email confirmation, resend, forgot/reset password;
  • password, authenticator-code, and recovery-code login inputs;
  • token refresh and secure token-pair storage;
  • profile, pending email, phone, password, and two-factor status;
  • authenticator setup and recovery-code display;
  • passkey listing/removal plus a .NET 10 JSON preview seam;
  • filtered personal data, account deletion, linked-provider management, and logout-all.

Token refresh is serialized, the access/refresh pair is persisted atomically, and an authentication epoch prevents an old login or refresh operation from restoring a session after logout or account switching. SecureStorage failures degrade safely to a memory-only or logged-out state rather than crashing the authentication lifecycle.

Passkeys and external providers

The server uses public ASP.NET Core Identity passkey APIs and the official temporary Identity ceremony cookie. The .NET 10 client displays sanitized registration options; native CreateAsync/AssertAsync completion is intentionally deferred to the .NET 11 MAUI passkey API.

External-provider discovery and safe unlinking are implemented. Browser challenge/callback/link handoff is deferred until a real provider can be configured rather than introducing a fake provider-specific flow.

Development email flow

Development replaces the no-op sender with a bounded, in-memory notification page. It allows confirmation and password-reset ceremonies to be exercised locally, is never persisted or logged, and is not mapped outside the Development environment. Production still requires a real IEmailSender<ApplicationUser>.

Test strategy

The change separates three levels of validation:

  • 15 ASP.NET Core integration tests using MSTest, WebApplicationFactory, and SQLite for stock, override, and new REST contracts.
  • 3 Microsoft Playwright tests for hosted registration, confirmation, login, forgot-password, and reset-password browser ceremonies.
  • 4 live Mac Catalyst DevFlow tests using the official experimental MAUI DevFlow agent and CDP bridge against the real MAUI Blazor Hybrid app and local server.

Run the full stack with:

cd 10.0/MauiBlazorWebIdentity
./scripts/run-devflow-live-tests.sh

The runner creates isolated SQLite state, starts the built server and Mac Catalyst executable, waits independently for the DevFlow agent and Blazor CDP bridge, runs all suites, and terminates only the exact processes it started. No Appium dependency is used.

Validated locally:

  • Identity API tests: 15/15 passed
  • Hosted Playwright tests: 3/3 passed
  • Live Mac Catalyst DevFlow tests: 4/4 passed
  • Web and Mac Catalyst builds succeeded
  • No server or MAUI process remained after the runner exited

Known existing warnings for Microsoft.OpenApi and SQLitePCLRaw.lib.e_sqlite3, plus host-dependent Mac Catalyst signing/provisioning, are documented and not changed here.

Potential ASP.NET Core Identity improvements

The capability and route matrices in IDENTITY_API_CAPABILITIES.md identify the following candidates for the in-box API surface:

  1. Machine-readable login outcomes

    • Distinguish invalid credentials, lockout, unconfirmed/not-allowed accounts, and two-factor-required responses without parsing problem text.
    • Continue emitting the normal in-box bearer response on success.
  2. A richer and unambiguous account-info contract

    • Expose confirmed/pending email, phone, password presence, two-factor status, authenticator configuration, recovery-code count, passkey count, and linked providers.
    • Require exactly one account mutation per request and support adding the first local password.
    • Provide a non-mutating two-factor status endpoint that never generates or reveals a shared key.
  3. First-class passkey REST ceremonies and management

    • JSON begin/finish endpoints for registration and assertion using Identity's existing passkey APIs and temporary ceremony scheme.
    • List, rename, and remove passkeys without recreating WebAuthn protocol logic in every client/server sample.
  4. Safe personal-data projection and deletion

    • A typed personal-data response that explicitly excludes password hashes, provider keys, authenticator secrets, tokens, and passkey public-key material.
    • Password-confirmed deletion plus a framework-defined recent-reauthentication contract for passwordless users.
  5. Linked-provider management

    • List and unlink external login methods while preventing removal of the final usable sign-in method.
    • A future provider-neutral browser handoff contract for native first-party clients.
  6. Session/token management semantics

    • An in-box logout-all operation based on security-stamp rotation.
    • Clear documentation that this invalidates refresh credentials, while already-issued opaque access tickets remain valid until expiry.
    • Longer-term opportunities include refresh-token replay detection, rotation, per-device/session registration, and targeted revocation.
  7. Configurable confirmation-link generation

    • Avoid assuming that stock Identity endpoints are mounted at one hard-coded prefix.
    • Make public origin/route generation work predictably behind proxies and when applications choose a different route group.

These additions are implemented only with public UserManager<TUser> and SignInManager<TUser> APIs and avoid MAUI-specific types, making the class library intentionally suitable for discussion or extraction into an ASP.NET Core contribution.

Documentation

  • IDENTITY_API_CAPABILITIES.md — hosted-feature classification and per-route test matrix.
  • IDENTITY_API_IMPLEMENTATION.md — implementation decisions, validation ledger, and known limitations.
  • DEVFLOW_TESTING.md — server, Playwright, and live MAUI DevFlow test architecture and prerequisites.

mattleibow and others added 14 commits September 12, 2026 02:40
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

This branch has not been deployed

No deployments
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