|
17 | 17 | <div class="row"> |
18 | 18 | <div class="col-lg-6"> |
19 | 19 | <section> |
20 | | - <PasskeyHandler CurrentRequestOptions="@currentPasskeyRequestOptions" OnResponse="LoginUserWithPasskey" OnError="SetPasskeyError" /> |
21 | 20 | <StatusMessage Message="@errorMessage" /> |
22 | 21 | <EditForm EditContext="editContext" method="post" OnSubmit="LoginUser" FormName="login"> |
23 | 22 | <DataAnnotationsValidator /> |
|
41 | 40 | </label> |
42 | 41 | </div> |
43 | 42 | <div> |
44 | | - <button type="submit" class="w-100 btn btn-lg btn-primary" disabled="@(currentPasskeyRequestOptions is not null)">Log in</button> |
| 43 | + <button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button> |
45 | 44 | </div> |
46 | 45 | <hr /> |
47 | 46 | <div class="d-flex flex-column"> |
48 | 47 | <span class="text-secondary mx-auto mt-2">OR</span> |
49 | | - <button type="submit" name="Input.UsePasskey" value="true" class="btn btn-link mx-auto" disabled="@(currentPasskeyRequestOptions is not null)">Log in with a passkey</button> |
| 48 | + <PasskeySubmit Operation="PasskeyOperation.Request" Name="Input.Passkey" EmailName="Input.Email" class="btn btn-link mx-auto">Log in with a passkey</PasskeySubmit> |
50 | 49 | </div> |
51 | 50 | <hr /> |
52 | 51 | <div> |
|
74 | 73 |
|
75 | 74 | @code { |
76 | 75 | private string? errorMessage; |
77 | | - |
78 | 76 | private EditContext editContext = default!; |
79 | | - private string? currentPasskeyRequestOptions; |
80 | 77 |
|
81 | 78 | [CascadingParameter] |
82 | 79 | private HttpContext HttpContext { get; set; } = default!; |
83 | 80 |
|
84 | | - [SupplyParameterFromForm(FormName = "login")] |
| 81 | + [SupplyParameterFromForm] |
85 | 82 | private InputModel Input { get; set; } = new(); |
86 | 83 |
|
87 | 84 | [SupplyParameterFromQuery] |
|
100 | 97 |
|
101 | 98 | public async Task LoginUser() |
102 | 99 | { |
103 | | - // When performing passkey sign-in, don't perform form validation. |
104 | | - // If provided, we use the email as a hint to suggest which user is likely being signed in. |
105 | | - if (Input.UsePasskey) |
| 100 | + if (!string.IsNullOrEmpty(Input.Passkey?.Error)) |
106 | 101 | { |
107 | | - var user = Input.Email is { Length: > 0 } email |
108 | | - ? await UserManager.FindByEmailAsync(email) |
109 | | - : null; |
110 | | - |
111 | | - var passkeyRequestArgs = new PasskeyRequestArgs<ApplicationUser> |
112 | | - { |
113 | | - User = user, |
114 | | - }; |
115 | | - var options = await SignInManager.ConfigurePasskeyRequestOptionsAsync(passkeyRequestArgs); |
116 | | - currentPasskeyRequestOptions = options.AsJson(); |
| 102 | + errorMessage = $"Error: Could not log in using the provided passkey: {Input.Passkey.Error}"; |
117 | 103 | return; |
118 | 104 | } |
119 | 105 |
|
120 | | - // If doing a password sign-in, validate the form. |
121 | | - if (!editContext.Validate()) |
| 106 | + SignInResult result; |
| 107 | + if (!string.IsNullOrEmpty(Input.Passkey?.CredentialJson)) |
122 | 108 | { |
123 | | - return; |
| 109 | + // When performing passkey sign-in, don't perform form validation. |
| 110 | + var options = await SignInManager.RetrievePasskeyRequestOptionsAsync(); |
| 111 | + if (options is null) |
| 112 | + { |
| 113 | + errorMessage = "Error: Could not complete passkey login. Please try again."; |
| 114 | + return; |
| 115 | + } |
| 116 | + |
| 117 | + result = await SignInManager.PasskeySignInAsync(Input.Passkey.CredentialJson, options); |
| 118 | + } |
| 119 | + else |
| 120 | + { |
| 121 | + // If doing a password sign-in, validate the form. |
| 122 | + if (!editContext.Validate()) |
| 123 | + { |
| 124 | + return; |
| 125 | + } |
| 126 | + |
| 127 | + // This doesn't count login failures towards account lockout |
| 128 | + // To enable password failures to trigger account lockout, set lockoutOnFailure: true |
| 129 | + result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); |
124 | 130 | } |
125 | 131 |
|
126 | | - // This doesn't count login failures towards account lockout |
127 | | - // To enable password failures to trigger account lockout, set lockoutOnFailure: true |
128 | | - var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); |
129 | 132 | if (result.Succeeded) |
130 | 133 | { |
131 | 134 | Logger.LogInformation("User logged in."); |
|
148 | 151 | } |
149 | 152 | } |
150 | 153 |
|
151 | | - public async Task LoginUserWithPasskey(string responseJson) |
152 | | - { |
153 | | - var options = await SignInManager.RetrievePasskeyRequestOptionsAsync(); |
154 | | - if (options is null) |
155 | | - { |
156 | | - errorMessage = "Error: Could not complete passkey login. Please try again."; |
157 | | - return; |
158 | | - } |
159 | | - |
160 | | - var result = await SignInManager.PasskeySignInAsync(responseJson, options); |
161 | | - if (result.Succeeded) |
162 | | - { |
163 | | - Logger.LogInformation("User logged in."); |
164 | | - RedirectManager.RedirectTo(ReturnUrl); |
165 | | - } |
166 | | - else |
167 | | - { |
168 | | - errorMessage = "Error: Could not log in using the provided passkey."; |
169 | | - return; |
170 | | - } |
171 | | - } |
172 | | - |
173 | | - public void SetPasskeyError(string? error) |
174 | | - { |
175 | | - errorMessage = $"Error: Could not log in using the provided passkey{(string.IsNullOrEmpty(error) ? "." : $": {error}")}"; |
176 | | - } |
177 | | - |
178 | 154 | private sealed class InputModel |
179 | 155 | { |
180 | 156 | [Required] |
|
188 | 164 | [Display(Name = "Remember me?")] |
189 | 165 | public bool RememberMe { get; set; } |
190 | 166 |
|
191 | | - public bool UsePasskey { get; set; } |
| 167 | + public PasskeyInputModel? Passkey { get; set; } |
192 | 168 | } |
193 | 169 | } |
0 commit comments