Skip to content
Merged
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
12 changes: 7 additions & 5 deletions src/Identity/Core/src/IdentityPasskeyOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,16 @@ public class IdentityPasskeyOptions
/// </para>
/// <para>
/// Possible values are "required", "preferred", and "discouraged".
/// If set to <see langword="null"/>, the effective value is "preferred".
/// </para>
/// <para>
/// If left <see langword="null"/>, the browser defaults to "preferred".
/// The default value is "required".
/// </para>
/// <para>
/// See <see href="https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement"/>.
/// </para>
/// </remarks>
public string? UserVerificationRequirement { get; set; }
public string? UserVerificationRequirement { get; set; } = "required";

/// <summary>
/// Gets or sets the extent to which the server desires to create a client-side discoverable credential.
Expand All @@ -87,16 +88,17 @@ public class IdentityPasskeyOptions
/// This option only applies when creating a new passkey, and is not enforced on the server.
/// </para>
/// <para>
/// Possible values are "discouraged", "preferred", or "required".
/// Possible values are "discouraged", "preferred", "required", or <see langword="null"/>.
/// If set to <see langword="null"/>, the effective value is "discouraged".
/// </para>
/// <para>
/// If left <see langword="null"/>, the browser defaults to "preferred".
/// The default value is "preferred".
/// </para>
/// <para>
/// See <see href="https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement"/>.
/// </para>
/// </remarks>
public string? ResidentKeyRequirement { get; set; }
public string? ResidentKeyRequirement { get; set; } = "preferred";

/// <summary>
/// Gets or sets the attestation conveyance preference.
Expand Down
4 changes: 0 additions & 4 deletions src/Identity/Extensions.Stores/src/IdentityPasskeyData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Identity;

Expand Down
4 changes: 2 additions & 2 deletions src/Identity/test/Identity.Test/IdentityPasskeyOptionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ public void VerifyDefaultOptions()

Assert.Equal(TimeSpan.FromMinutes(5), options.AuthenticatorTimeout);
Assert.Equal(32, options.ChallengeSize);
Assert.Equal("preferred", options.ResidentKeyRequirement);
Assert.Equal("required", options.UserVerificationRequirement);
Assert.Null(options.ServerDomain);
Assert.Null(options.UserVerificationRequirement);
Assert.Null(options.ResidentKeyRequirement);
Assert.Null(options.AttestationConveyancePreference);
Assert.Null(options.AuthenticatorAttachment);
Assert.Null(options.IsAllowedAlgorithm);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,10 @@ private sealed class AssertionTest : PasskeyScenarioTest<PasskeyAssertionResult<
{
private static readonly byte[] _defaultCredentialId = [1, 2, 3, 4, 5, 6, 7, 8];

public IdentityPasskeyOptions PasskeyOptions { get; } = new();
public IdentityPasskeyOptions PasskeyOptions { get; } = new()
{
UserVerificationRequirement = "preferred",
};
public string Origin { get; set; } = "https://example.com";
public PocoUser User { get; set; } = new()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,10 @@ private sealed class AttestationTest : PasskeyScenarioTest<PasskeyAttestationRes
private static readonly byte[] _defaultAaguid = new byte[16];
private static readonly byte[] _defaultAttestationStatement = [0xA0]; // Empty CBOR map

public IdentityPasskeyOptions PasskeyOptions { get; } = new();
public IdentityPasskeyOptions PasskeyOptions { get; } = new()
{
UserVerificationRequirement = "preferred",
};
public string? UserId { get; set; } = "df0a3af4-bd65-440f-82bd-5b839e300dcd";
public string? UserName { get; set; } = "johndoe";
public string? UserDisplayName { get; set; } = "John Doe";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,20 @@ else

<form @formname="add-passkey" @onsubmit="AddPasskey" method="post">
<AntiforgeryToken />
<PasskeySubmit Operation="PasskeyOperation.Create" Name="Input" class="btn btn-primary">Add a new passkey</PasskeySubmit>
@if (currentPasskeys is { Count: >= MaxPasskeyCount })
{
<p class="text-danger">You have reached the maximum number of allowed passkeys. Please delete one before adding a new one.</p>
}
else
{
<PasskeySubmit Operation="PasskeyOperation.Create" Name="Input" class="btn btn-primary">Add a new passkey</PasskeySubmit>
}

</form>

@code {
private const int MaxPasskeyCount = 100;

private ApplicationUser? user;
private IList<UserPasskeyInfo>? currentPasskeys;

Expand Down Expand Up @@ -100,6 +110,12 @@ else
return;
}

if (currentPasskeys!.Count >= MaxPasskeyCount)
{
RedirectManager.RedirectToCurrentPageWithStatus($"Error: You have reached the maximum number of allowed passkeys.", HttpContext);
return;
}

var attestationResult = await SignInManager.PerformPasskeyAttestationAsync(Input.CredentialJson);
if (!attestationResult.Succeeded)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
private sealed class InputModel
{
[Required]
[StringLength(200, ErrorMessage = "Passkey names must be no longer than {1} characters.")]
public string Name { get; set; } = "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@

protected override void OnInitialized()
{
tokens = Services.GetRequiredService<IAntiforgery>()?.GetTokens(HttpContext);
tokens = Services.GetService<IAntiforgery>()?.GetTokens(HttpContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//#if (UseLocalDB)
// "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-BlazorWebCSharp__1-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true"
//#else
// "DefaultConnection": "DataSource=Data\\app.db;Cache=Shared"
// "DefaultConnection": "DataSource=Data/app.db;Cache=Shared"
//#endif
// },
////#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@ await page.EvaluateAsync("""
await page.ClickAsync("text=Add a new passkey");

await page.WaitForSelectorAsync("text=Enter a name for your passkey");

// First check that we can't register a passkey with a long name.
var longName = new string('a', count: 201);
await page.FillAsync("[name=\"Input.Name\"]", longName);
await page.ClickAsync("text=Continue");
await page.WaitForSelectorAsync("text=Passkey names must be no longer than 200 characters.");

// Now register a passkey with a valid name
await page.FillAsync("[name=\"Input.Name\"]", "My passkey");
await page.ClickAsync("text=Continue");

Expand Down
Loading