Skip to content

Passkey design follow-ups #62530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 220 additions & 144 deletions src/Identity/Core/src/DefaultPasskeyHandler.cs

Large diffs are not rendered by default.

33 changes: 26 additions & 7 deletions src/Identity/Core/src/IPasskeyHandler.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Http;

namespace Microsoft.AspNetCore.Identity;

/// <summary>
/// Represents a handler for passkey assertion and attestation.
/// Represents a handler for generating passkey creation and request options and performing
/// passkey assertion and attestation.
/// </summary>
public interface IPasskeyHandler<TUser>
where TUser : class
{
/// <summary>
/// Performs passkey attestation using the provided credential JSON and original options JSON.
/// Generates passkey creation options for the specified user entity and HTTP context.
/// </summary>
/// <param name="userEntity">The passkey user entity for which to generate creation options.</param>
/// <param name="httpContext">The HTTP context associated with the request.</param>
/// <returns>A <see cref="PasskeyCreationOptionsResult"/> representing the result.</returns>
Task<PasskeyCreationOptionsResult> MakeCreationOptionsAsync(PasskeyUserEntity userEntity, HttpContext httpContext);

/// <summary>
/// Generates passkey request options for the specified user and HTTP context.
/// </summary>
/// <param name="user">The user for whom to generate request options.</param>
/// <param name="httpContext">The HTTP context associated with the request.</param>
/// <returns>A <see cref="PasskeyRequestOptionsResult"/> representing the result.</returns>
Task<PasskeyRequestOptionsResult> MakeRequestOptionsAsync(TUser? user, HttpContext httpContext);

/// <summary>
/// Performs passkey attestation using the provided <see cref="PasskeyAttestationContext"/>.
/// </summary>
/// <param name="context">The context containing necessary information for passkey attestation.</param>
/// <returns>A task object representing the asynchronous operation containing the <see cref="PasskeyAttestationResult"/>.</returns>
Task<PasskeyAttestationResult> PerformAttestationAsync(PasskeyAttestationContext<TUser> context);
/// <returns>A <see cref="PasskeyAttestationResult"/> representing the result.</returns>
Task<PasskeyAttestationResult> PerformAttestationAsync(PasskeyAttestationContext context);

/// <summary>
/// Performs passkey assertion using the provided credential JSON, original options JSON, and optional user.
/// Performs passkey assertion using the provided <see cref="PasskeyAssertionContext"/>.
/// </summary>
/// <param name="context">The context containing necessary information for passkey assertion.</param>
/// <returns>A task object representing the asynchronous operation containing the <see cref="PasskeyAssertionResult{TUser}"/>.</returns>
Task<PasskeyAssertionResult<TUser>> PerformAssertionAsync(PasskeyAssertionContext<TUser> context);
/// <returns>A <see cref="PasskeyAssertionResult{TUser}"/> representing the result.</returns>
Task<PasskeyAssertionResult<TUser>> PerformAssertionAsync(PasskeyAssertionContext context);
}
2 changes: 2 additions & 0 deletions src/Identity/Core/src/IdentityJsonSerializerContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.Identity;
[JsonSerializable(typeof(PublicKeyCredentialRequestOptions))]
[JsonSerializable(typeof(PublicKeyCredential<AuthenticatorAssertionResponse>))]
[JsonSerializable(typeof(PublicKeyCredential<AuthenticatorAttestationResponse>))]
[JsonSerializable(typeof(PasskeyAttestationState))]
[JsonSerializable(typeof(PasskeyAssertionState))]
[JsonSourceGenerationOptions(
JsonSerializerDefaults.Web,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Expand Down
26 changes: 9 additions & 17 deletions src/Identity/Core/src/PasskeyAssertionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ namespace Microsoft.AspNetCore.Identity;
/// <summary>
/// Represents the context for passkey assertion.
/// </summary>
/// <typeparam name="TUser">The type of user associated with the passkey.</typeparam>
public sealed class PasskeyAssertionContext<TUser>
where TUser : class
public sealed class PasskeyAssertionContext
{
/// <summary>
/// Gets or sets the user associated with the passkey, if known.
/// Gets or sets the <see cref="Http.HttpContext"/> for the current request.
/// </summary>
public TUser? User { get; init; }
public required HttpContext HttpContext { get; init; }

/// <summary>
/// Gets or sets the credentials obtained by JSON-serializing the result of the
Expand All @@ -24,17 +22,11 @@ public sealed class PasskeyAssertionContext<TUser>
public required string CredentialJson { get; init; }

/// <summary>
/// Gets or sets the JSON representation of the original passkey creation options provided to the browser.
/// </summary>
public required string OriginalOptionsJson { get; init; }

/// <summary>
/// Gets or sets the <see cref="UserManager{TUser}"/> to retrieve user information from.
/// Gets or sets the state to be used in the assertion procedure.
/// </summary>
public required UserManager<TUser> UserManager { get; init; }

/// <summary>
/// Gets or sets the <see cref="HttpContext"/> for the current request.
/// </summary>
public required HttpContext HttpContext { get; init; }
/// <remarks>
/// This is expected to match the <see cref="PasskeyRequestOptionsResult.AssertionState"/>
/// previously returned from <see cref="IPasskeyHandler{TUser}.MakeRequestOptionsAsync(TUser, HttpContext)"/>.
/// </remarks>
public required string? AssertionState { get; init; }
}
27 changes: 12 additions & 15 deletions src/Identity/Core/src/PasskeyAttestationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,25 @@ namespace Microsoft.AspNetCore.Identity;
/// <summary>
/// Represents the context for passkey attestation.
/// </summary>
/// <typeparam name="TUser">The type of user associated with the passkey.</typeparam>
public sealed class PasskeyAttestationContext<TUser>
where TUser : class
public sealed class PasskeyAttestationContext
{
/// <summary>
/// Gets or sets the credentials obtained by JSON-serializing the result of the
/// <c>navigator.credentials.create()</c> JavaScript function.
/// </summary>
public required string CredentialJson { get; init; }

/// <summary>
/// Gets or sets the JSON representation of the original passkey creation options provided to the browser.
/// Gets or sets the <see cref="Http.HttpContext"/> for the current request.
/// </summary>
public required string OriginalOptionsJson { get; init; }
public required HttpContext HttpContext { get; init; }

/// <summary>
/// Gets or sets the <see cref="UserManager{TUser}"/> to retrieve user information from.
/// Gets or sets the credentials obtained by JSON-serializing the result of the
/// <c>navigator.credentials.create()</c> JavaScript function.
/// </summary>
public required UserManager<TUser> UserManager { get; init; }
public required string CredentialJson { get; init; }

/// <summary>
/// Gets or sets the <see cref="HttpContext"/> for the current request.
/// Gets or sets the state to be used in the attestation procedure.
/// </summary>
public required HttpContext HttpContext { get; init; }
/// <remarks>
/// This is expected to match the <see cref="PasskeyCreationOptionsResult.AttestationState"/>
/// previously returned from <see cref="IPasskeyHandler{TUser}.MakeCreationOptionsAsync(PasskeyUserEntity, HttpContext)"/>.
/// </remarks>
public required string? AttestationState { get; init; }
}
14 changes: 11 additions & 3 deletions src/Identity/Core/src/PasskeyAttestationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public sealed class PasskeyAttestationResult
/// Gets whether the attestation was successful.
/// </summary>
[MemberNotNullWhen(true, nameof(Passkey))]
[MemberNotNullWhen(true, nameof(UserEntity))]
[MemberNotNullWhen(false, nameof(Failure))]
public bool Succeeded { get; }

Expand All @@ -22,15 +23,21 @@ public sealed class PasskeyAttestationResult
/// </summary>
public UserPasskeyInfo? Passkey { get; }

/// <summary>
/// Gets the user entity associated with the passkey when successful.
/// </summary>
public PasskeyUserEntity? UserEntity { get; }

/// <summary>
/// Gets the error that occurred during attestation.
/// </summary>
public PasskeyException? Failure { get; }

private PasskeyAttestationResult(UserPasskeyInfo passkey)
private PasskeyAttestationResult(UserPasskeyInfo passkey, PasskeyUserEntity userEntity)
{
Succeeded = true;
Passkey = passkey;
UserEntity = userEntity;
}

private PasskeyAttestationResult(PasskeyException failure)
Expand All @@ -43,11 +50,12 @@ private PasskeyAttestationResult(PasskeyException failure)
/// Creates a successful result for a passkey attestation operation.
/// </summary>
/// <param name="passkey">The passkey information associated with the attestation.</param>
/// <param name="userEntity">The user entity associated with the attestation.</param>
/// <returns>A <see cref="PasskeyAttestationResult"/> instance representing a successful attestation.</returns>
public static PasskeyAttestationResult Success(UserPasskeyInfo passkey)
public static PasskeyAttestationResult Success(UserPasskeyInfo passkey, PasskeyUserEntity userEntity)
{
ArgumentNullException.ThrowIfNull(passkey);
return new PasskeyAttestationResult(passkey);
return new PasskeyAttestationResult(passkey, userEntity);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Http;

namespace Microsoft.AspNetCore.Identity;

/// <summary>
/// Contains the context for passkey attestation statement verification.
/// </summary>
/// <remarks>
/// See <see href="https://www.w3.org/TR/webauthn-3/#verification-procedure"/>.
/// </remarks>
public readonly struct PasskeyAttestationStatementVerificationContext
{
/// <summary>
/// Gets or sets the <see cref="HttpContext"/> for the current request.
/// </summary>
public required HttpContext HttpContext { get; init; }

/// <summary>
/// Gets or sets the attestation object as a byte array.
/// </summary>
/// <remarks>
/// See <see href="https://www.w3.org/TR/webauthn-3/#attestation-object"/>.
/// </remarks>
public required ReadOnlyMemory<byte> AttestationObject { get; init; }

/// <summary>
/// Gets or sets the hash of the client data as a byte array.
/// </summary>
public required ReadOnlyMemory<byte> ClientDataHash { get; init; }
}
46 changes: 0 additions & 46 deletions src/Identity/Core/src/PasskeyCreationArgs.cs

This file was deleted.

45 changes: 0 additions & 45 deletions src/Identity/Core/src/PasskeyCreationOptions.cs

This file was deleted.

28 changes: 28 additions & 0 deletions src/Identity/Core/src/PasskeyCreationOptionsResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Identity;

/// <summary>
/// Represents the result of a passkey creation options generation.
/// </summary>
public sealed class PasskeyCreationOptionsResult
{
/// <summary>
/// Gets or sets the JSON representation of the creation options.
/// </summary>
/// <remarks>
/// The structure of this JSON is compatible with
/// <see href="https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialcreationoptionsjson"/>
/// and should be used with the <c>navigator.credentials.create()</c> JavaScript API.
/// </remarks>
public required string CreationOptionsJson { get; init; }

/// <summary>
/// Gets or sets the state to be used in the attestation procedure.
/// </summary>
/// <remarks>
/// This can be later retrieved during assertion with <see cref="PasskeyAttestationContext.AttestationState"/>.
/// </remarks>
public string? AttestationState { get; init; }
}
Loading
Loading