diff --git a/src/Identity/Core/src/IdentityPasskeyOptions.cs b/src/Identity/Core/src/IdentityPasskeyOptions.cs index e8827cd18a71..d83ff848aaab 100644 --- a/src/Identity/Core/src/IdentityPasskeyOptions.cs +++ b/src/Identity/Core/src/IdentityPasskeyOptions.cs @@ -69,15 +69,16 @@ public class IdentityPasskeyOptions /// /// /// Possible values are "required", "preferred", and "discouraged". + /// If set to , the effective value is "preferred". /// /// - /// If left , the browser defaults to "preferred". + /// The default value is "required". /// /// /// See . /// /// - public string? UserVerificationRequirement { get; set; } + public string? UserVerificationRequirement { get; set; } = "required"; /// /// Gets or sets the extent to which the server desires to create a client-side discoverable credential. @@ -87,16 +88,17 @@ public class IdentityPasskeyOptions /// This option only applies when creating a new passkey, and is not enforced on the server. /// /// - /// Possible values are "discouraged", "preferred", or "required". + /// Possible values are "discouraged", "preferred", "required", or . + /// If set to , the effective value is "discouraged". /// /// - /// If left , the browser defaults to "preferred". + /// The default value is "preferred". /// /// /// See . /// /// - public string? ResidentKeyRequirement { get; set; } + public string? ResidentKeyRequirement { get; set; } = "preferred"; /// /// Gets or sets the attestation conveyance preference. diff --git a/src/Identity/Extensions.Stores/src/IdentityPasskeyData.cs b/src/Identity/Extensions.Stores/src/IdentityPasskeyData.cs index 4fee0dd892d3..16930dde1e40 100644 --- a/src/Identity/Extensions.Stores/src/IdentityPasskeyData.cs +++ b/src/Identity/Extensions.Stores/src/IdentityPasskeyData.cs @@ -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; diff --git a/src/Identity/test/Identity.Test/IdentityPasskeyOptionsTest.cs b/src/Identity/test/Identity.Test/IdentityPasskeyOptionsTest.cs index c539eb35e56b..e9aa6cf31237 100644 --- a/src/Identity/test/Identity.Test/IdentityPasskeyOptionsTest.cs +++ b/src/Identity/test/Identity.Test/IdentityPasskeyOptionsTest.cs @@ -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); diff --git a/src/Identity/test/Identity.Test/Passkeys/PasskeyHandlerAssertionTest.cs b/src/Identity/test/Identity.Test/Passkeys/PasskeyHandlerAssertionTest.cs index 0e603eedb047..0b368969c004 100644 --- a/src/Identity/test/Identity.Test/Passkeys/PasskeyHandlerAssertionTest.cs +++ b/src/Identity/test/Identity.Test/Passkeys/PasskeyHandlerAssertionTest.cs @@ -980,7 +980,10 @@ private sealed class AssertionTest : PasskeyScenarioTest - Add a new passkey + @if (currentPasskeys is { Count: >= MaxPasskeyCount }) + { +

You have reached the maximum number of allowed passkeys. Please delete one before adding a new one.

+ } + else + { + Add a new passkey + } + @code { + private const int MaxPasskeyCount = 100; + private ApplicationUser? user; private IList? currentPasskeys; @@ -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) { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor index 80595b5bcc1e..a28d1bb85a50 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor @@ -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; } = ""; } } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Shared/PasskeySubmit.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Shared/PasskeySubmit.razor index f3d6f52c21be..9db61ffa5f3d 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Shared/PasskeySubmit.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Shared/PasskeySubmit.razor @@ -35,6 +35,6 @@ protected override void OnInitialized() { - tokens = Services.GetRequiredService()?.GetTokens(HttpContext); + tokens = Services.GetService()?.GetTokens(HttpContext); } } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/appsettings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/appsettings.json index 2924c11940bb..3aa431a5deac 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/appsettings.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/appsettings.json @@ -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 diff --git a/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorTemplateTest.cs b/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorTemplateTest.cs index ddc5ee58c81c..f6334c1cca6a 100644 --- a/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorTemplateTest.cs +++ b/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorTemplateTest.cs @@ -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");