From 9258b3eec452e257b4732d1a278cbbc9a4515546 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Mon, 28 Jul 2025 15:32:16 -0400 Subject: [PATCH 1/8] Add passkey limits --- .../Components/Account/Pages/Manage/Passkeys.razor | 13 ++++++++++++- .../Account/Pages/Manage/RenamePasskey.razor | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor index 283f26de1897..69bdd5e374b8 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor @@ -48,10 +48,15 @@ else
- Add a new passkey + @if (currentPasskeys is not { Count: >= MaxPasskeyCount }) + { + Add a new passkey + } @code { + private const int MaxPasskeyCount = 100; + private ApplicationUser? user; private IList? currentPasskeys; @@ -100,6 +105,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..ee59cd2df67a 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] + [MaxLength(200, ErrorMessage = "Passkey names must be no longer than 200 characters.")] public string Name { get; set; } = ""; } } From 341008fb00c8b0cb1f8e24dcc6154824af2e7e88 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Mon, 28 Jul 2025 15:58:49 -0400 Subject: [PATCH 2/8] Update RenamePasskey.razor --- .../Components/Account/Pages/Manage/RenamePasskey.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ee59cd2df67a..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,7 +89,7 @@ private sealed class InputModel { [Required] - [MaxLength(200, ErrorMessage = "Passkey names must be no longer than 200 characters.")] + [StringLength(200, ErrorMessage = "Passkey names must be no longer than {1} characters.")] public string Name { get; set; } = ""; } } From 05aa9516e87a9d943351e177c60c25eea663caaa Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 7 Aug 2025 15:24:45 -0400 Subject: [PATCH 3/8] Update IdentityPasskeyOptions.cs --- src/Identity/Core/src/IdentityPasskeyOptions.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Identity/Core/src/IdentityPasskeyOptions.cs b/src/Identity/Core/src/IdentityPasskeyOptions.cs index e8827cd18a71..e64be103db93 100644 --- a/src/Identity/Core/src/IdentityPasskeyOptions.cs +++ b/src/Identity/Core/src/IdentityPasskeyOptions.cs @@ -87,16 +87,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. From 09518920e8cccd77e7a0207e8f18b8db83c1d21e Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 7 Aug 2025 16:27:28 -0400 Subject: [PATCH 4/8] PR feedback --- .../Components/Account/Pages/Manage/Passkeys.razor | 7 ++++++- .../test/Templates.Blazor.Tests/BlazorTemplateTest.cs | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor index 69bdd5e374b8..b4bc96b4f376 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor @@ -48,10 +48,15 @@ else
- @if (currentPasskeys is not { Count: >= MaxPasskeyCount }) + @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 { 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"); From 8136c91e70312eb030c586ba1972744ce20b4583 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Fri, 8 Aug 2025 12:52:55 -0400 Subject: [PATCH 5/8] Make user verification required by default --- src/Identity/Core/src/IdentityPasskeyOptions.cs | 5 +++-- .../test/Identity.Test/IdentityPasskeyOptionsTest.cs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Identity/Core/src/IdentityPasskeyOptions.cs b/src/Identity/Core/src/IdentityPasskeyOptions.cs index e64be103db93..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. 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); From f0420ad30f5fff3387005ade875675bf2f91af62 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Fri, 8 Aug 2025 13:15:49 -0400 Subject: [PATCH 6/8] Update local DB connection string --- .../content/BlazorWeb-CSharp/BlazorWebCSharp.1/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 67e2433ccdbe55a7462d6d7473734764d32d0684 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Fri, 8 Aug 2025 13:53:27 -0400 Subject: [PATCH 7/8] Backport changes from preview7 --- src/Identity/Extensions.Stores/src/IdentityPasskeyData.cs | 4 ---- .../Components/Account/Shared/PasskeySubmit.razor | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) 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/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); } } From 0aedc366e5ea385d9f634f46a61ee3e70d20fe57 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Mon, 11 Aug 2025 11:38:22 -0400 Subject: [PATCH 8/8] Fix tests --- .../Identity.Test/Passkeys/PasskeyHandlerAssertionTest.cs | 5 ++++- .../Identity.Test/Passkeys/PasskeyHandlerAttestationTest.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) 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