Skip to content

Document IPasskeyHandler and attestation state integrity/ownership - #37444

Open
wadepickett with Copilot wants to merge 8 commits into
mainfrom
copilot/document-ipasskeyhandler
Open

Document IPasskeyHandler and attestation state integrity/ownership#37444
wadepickett with Copilot wants to merge 8 commits into
mainfrom
copilot/document-ipasskeyhandler

Conversation

Copilot AI commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

The passkeys article only documented the SignInManager registration path, leaving IPasskeyHandler<TUser> and the attestation state undocumented. This is a security gap: apps using the handler directly take over storing the state, which is unsigned plain JSON that decides which account a new passkey attaches to — round-tripping it through the browser unprotected enables account takeover by editing the user ID.

Changes

Added a new H2, "Customize passkey handling with IPasskeyHandler<TUser>", to aspnetcore/security/authentication/passkeys/index.md, between "Custom origin validation" and "Registration flow" (customization sections ordered least-to-most invasive).

  • Default path — recommends SignInManager; explains it stores/clears state in a data-protected auth cookie.
  • The handler — the four methods (MakeCreationOptionsAsync, MakeRequestOptionsAsync, PerformAttestationAsync, PerformAssertionAsync) and when to implement/wrap it.
  • Attestation stateCreationOptionsJson (browser) vs. AttestationState (app-held), carrying the challenge and PasskeyUserEntity; [!WARNING] on the lack of integrity protection.
  • Integrity/ownership — table of three requirements (tamper-proof, session/user-bound, single-use), the attack each prevents, and SignInManager reference behavior.
  • Registration example — two endpoints: data-protect + server-side store the state, then recheck session/user, verify PasskeyAttestationResult.UserEntity, and clear the state.
  • DI — where IPasskeyHandler<TUser> is registered and how to replace it.
  • Frontmatter: added ai-usage: ai-assisted, updated ms.date.

All API references use verified <xref> IDs sourced from the product source. No new file or redirection needed.


Internal previews

File Preview link
aspnetcore/security/authentication/passkeys/index.md aspnetcore/security/authentication/passkeys/index

Copilot AI and others added 2 commits August 8, 2026 23:08
Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com>
Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com>
Copilot AI changed the title [WIP] Document IPasskeyHandler and attestation state validation Document IPasskeyHandler and attestation state integrity/ownership Aug 8, 2026
Copilot AI requested a review from wadepickett August 8, 2026 23:09
Comment thread aspnetcore/security/authentication/passkeys/index.md Outdated
Comment thread aspnetcore/security/authentication/passkeys/index.md Outdated
Comment thread aspnetcore/security/authentication/passkeys/index.md Outdated
Co-authored-by: Wade Pickett <wpickett@microsoft.com>
@wadepickett

wadepickett commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Testing out a copilot instruction that we may add into the repo copilot instructions or skill.md below. Moving code comments out of the code and addressing as a bulleted list after the code. Also diffentiating between explanatory comments and structural placeholder comments.

@wadepickett

Copy link
Copy Markdown
Contributor

@copilot: Complete the task below per the guidance in the task:

Task: Move localizable explanatory comments out of C# code blocks in the passkeys article

File: aspnetcore/security/authentication/passkeys/index.md

Scope: The ## Customize passkey handling with IPasskeyHandler<TUser> section and its code examples (the two registration endpoints and the handler registration snippet). Only touch code blocks that were added in PR #37444 — don't rewrite the existing Registration flow / Authentication flow examples.

Why

Explanatory comments inside fenced code blocks are not localized into other languages (Spanish, French, etc.). Guidance that helps the reader understand why a step matters belongs in the article prose so it gets translated. This also matches the repo's established pattern of following a code block with an In the preceding code: or The preceding code: bulleted list.

What to do

For each affected C# code block:

  1. Identify explanatory comments — comments that explain why a step is done, describe a security consideration, or restate what the code accomplishes. These must move out.
  2. Remove those comments from the code block and rewrite their content as a bulleted list in the article body, immediately after the closing code fence of that code block, introduced with The preceding code: (or In the preceding code: when referring to specific elements).
    • Use complete sentences ending with periods.
    • Use * as the bullet marker (repo convention).
    • Keep the meaning intact — especially the security points (never send raw state to the browser, bind to session/user, single-use/clear the state, verify the user entity matches).
  3. Keep minimal structural placeholder comments only — a short comment is acceptable only when it marks where the reader must insert their own implementation (for example, a {PROTECTED STATE STORE} read/write that has no code line). Keep those terse; don't let them carry the explanation.
  4. Preserve all code logic, <xref> links, backticked identifiers, and the {PROTECTED STATE STORE} placeholder format.
  5. Don't introduce version numbers in prose (monikers handle versioning) and keep sentence case.

Apply this to

  • The first endpoint (POST /Account/PasskeyCreationOptions) — comments around protecting/storing the state.
  • The second endpoint (POST /Account/PasskeyCreation) — comments around reading state back, checking session/user, verifying the returned user entity, and clearing the state.
  • The Register the handler snippet — if any explanatory comments are present.

Example (before → after)

Before:

    // Protect the attestation state and store it server-side, bound to the
    // current session and user. Never send the raw state to the browser.
    var protector = dataProtectionProvider.CreateProtector("Passkeys.Attestation");
    var protectedState = protector.Protect(optionsResult.AttestationState ?? string.Empty);

    // Store 'protectedState', 'userId', and a short expiration keyed by the
    // session ID in '{PROTECTED STATE STORE}'.

    return TypedResults.Content(
        optionsResult.CreationOptionsJson, contentType: "application/json");
});

After:

    var protector = dataProtectionProvider.CreateProtector("Passkeys.Attestation");
    var protectedState = protector.Protect(optionsResult.AttestationState ?? string.Empty);

    // Store 'protectedState', 'userId', and a short expiration in
    // '{PROTECTED STATE STORE}', keyed by the session ID.

    return TypedResults.Content(
        optionsResult.CreationOptionsJson, contentType: "application/json");
});

The preceding code:

  • Protects the attestation state and stores it server-side, bound to the current session and user. The raw state is never sent to the browser.
  • Stores the protected state, the user ID, and a short expiration in the {PROTECTED STATE STORE}, keyed by the session ID, where the {PROTECTED STATE STORE} placeholder is the app's server-side store.

After making the changes

  • Verify each moved bullet is a complete sentence ending with a period.
  • Confirm no explanatory (non-structural) comments remain inside the affected code blocks.
  • Update ms.date to today's date in MM/DD/YYYY format if more than 50 characters changed (the file already has ai-usage: ai-assisted, so no frontmatter addition is needed).

Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com>

Copilot AI commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

@copilot: Complete the task below per the guidance in the task:

Task: Move localizable explanatory comments out of C# code blocks in the passkeys article

...

Done in d2ea7fc. Moved the explanatory comments out of both registration endpoint code blocks into The preceding code: bulleted lists, preserving the security points (raw state never sent to the browser, session/user binding, verifying the user entity matches, single-use/clear). Kept only terse structural placeholder comments marking {PROTECTED STATE STORE} reads/writes. The Register the handler snippet had no explanatory comments. Also updated ms.date.

Copilot AI requested a review from wadepickett August 10, 2026 17:16
@wadepickett
wadepickett marked this pull request as ready for review August 10, 2026 17:21
@wadepickett
wadepickett requested a review from rolandVi August 10, 2026 17:23
@wadepickett

Copy link
Copy Markdown
Contributor

@rolandVi, could you review this new entry related to your issue #37437? Thanks again for creating the issue and providing details!

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

This PR expands the passkeys documentation to cover direct use of IPasskeyHandler<TUser> and the security responsibilities that come with app-managed attestation state, addressing the documented account-takeover risk when state is round-tripped through the browser without integrity protections (Fixes #37437).

Changes:

  • Added a new section describing when and how to use IPasskeyHandler<TUser> vs. the recommended SignInManager path.
  • Documented what attestation state is, why it’s security-sensitive, and the integrity/ownership requirements apps must enforce.
  • Added a minimal API example showing server-side protected storage of attestation state and revalidation on return, plus updated front matter (ai-usage, ms.date).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread aspnetcore/security/authentication/passkeys/index.md
Comment thread aspnetcore/security/authentication/passkeys/index.md
Comment thread aspnetcore/security/authentication/passkeys/index.md Outdated
Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com>
wadepickett and others added 2 commits August 10, 2026 12:18
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Without checking protectedState the "no state exists" path throws a 500 instead of a graceful rejection.  Changing code to match the documented behavoir.

@wadepickett wadepickett 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.

Reviewed, made many updates and now approved. However it would be good to have a review from the development team before merging.

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.

Passkeys: document IPasskeyHandler and how to validate attestation state integrity and ownership

3 participants