Skip to content

Commit 05507ce

Browse files
committed
Log passkey logins differently. No Input form data in this case.
1 parent 2c70cc5 commit 05507ce

File tree

1 file changed

+152
-135
lines changed
  • ControlR.Web.Server/Components/Account/Pages

1 file changed

+152
-135
lines changed

ControlR.Web.Server/Components/Account/Pages/Login.razor

Lines changed: 152 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -14,163 +14,180 @@
1414
<MudText Typo="Typo.h3" GutterBottom="true">Log in</MudText>
1515

1616
<MudGrid>
17-
<MudItem md="6">
18-
<StatusMessage Message="@_errorMessage" />
19-
<EditForm EditContext="_editContext" method="post" OnSubmit="LoginUser" FormName="login">
20-
<DataAnnotationsValidator />
21-
22-
<MudText GutterBottom="true" Typo="Typo.body1" Color="Color.Primary">
23-
Use a local account to log in.
24-
</MudText>
25-
26-
<MudGrid>
27-
<MudItem xs="12">
28-
<MudStaticTextField For="@(() => Input.Email)" @bind-Value="Input.Email" Label="Email"
29-
Placeholder="name@example.com"
30-
UserAttributes="@(new Dictionary<string, object?> { { "autocomplete", "username webauthn" }, { "aria-required", "true" } })" />
31-
</MudItem>
32-
<MudItem xs="12">
33-
<MudStaticTextField For="@(() => Input.Password)" @bind-Value="Input.Password" Label="Password"
34-
InputType="InputType.Password" Placeholder="password"
35-
UserAttributes="@(new Dictionary<string, object?> { { "autocomplete", "current-password" }, { "aria-required", "true" } })" />
36-
</MudItem>
37-
<MudItem xs="12">
38-
<MudStaticCheckBox For="@(() => Input.RememberMe)" @bind-Value="Input.RememberMe">Remember me
39-
</MudStaticCheckBox>
40-
</MudItem>
41-
<MudItem xs="12" Class="d-flex gap-4">
42-
<MudStaticButton Variant="Variant.Outlined" Color="Color.Primary" FormAction="FormAction.Submit">Log
43-
in</MudStaticButton>
44-
<PasskeySubmit Operation="PasskeyOperation.Request" Name="Input.Passkey" EmailName="Input.Email"
45-
Variant="Variant.Outlined" Color="Color.Secondary">
46-
Sign in with a passkey
47-
</PasskeySubmit>
48-
</MudItem>
49-
</MudGrid>
50-
</EditForm>
51-
52-
@if (!string.IsNullOrEmpty(Input.Passkey?.Error))
17+
<MudItem md="6">
18+
<StatusMessage Message="@_errorMessage" />
19+
<EditForm EditContext="_editContext" method="post" OnSubmit="LoginUser" FormName="login">
20+
<DataAnnotationsValidator />
21+
22+
<MudText GutterBottom="true" Typo="Typo.body1" Color="Color.Primary">
23+
Use a local account to log in.
24+
</MudText>
25+
26+
<MudGrid>
27+
<MudItem xs="12">
28+
<MudStaticTextField For="@(() => Input.Email)" @bind-Value="Input.Email" Label="Email"
29+
Placeholder="name@example.com"
30+
UserAttributes="@(new Dictionary<string, object?> { { "autocomplete", "username webauthn" }, { "aria-required", "true" } })" />
31+
</MudItem>
32+
<MudItem xs="12">
33+
<MudStaticTextField For="@(() => Input.Password)" @bind-Value="Input.Password" Label="Password"
34+
InputType="InputType.Password" Placeholder="password"
35+
UserAttributes="@(new Dictionary<string, object?> { { "autocomplete", "current-password" }, { "aria-required", "true" } })" />
36+
</MudItem>
37+
<MudItem xs="12">
38+
<MudStaticCheckBox For="@(() => Input.RememberMe)" @bind-Value="Input.RememberMe">Remember me
39+
</MudStaticCheckBox>
40+
</MudItem>
41+
<MudItem xs="12" Class="d-flex gap-4">
42+
<MudStaticButton Variant="Variant.Outlined" Color="Color.Primary" FormAction="FormAction.Submit">Log
43+
in</MudStaticButton>
44+
<PasskeySubmit Operation="PasskeyOperation.Request" Name="Input.Passkey" EmailName="Input.Email"
45+
Variant="Variant.Outlined" Color="Color.Secondary">
46+
Sign in with a passkey
47+
</PasskeySubmit>
48+
</MudItem>
49+
</MudGrid>
50+
</EditForm>
51+
52+
@if (!string.IsNullOrEmpty(Input.Passkey?.Error))
53+
{
54+
<MudAlert Severity="Severity.Error" Variant="Variant.Text" Class="mt-4">
55+
@Input.Passkey.Error
56+
</MudAlert>
57+
}
58+
59+
<MudGrid Class="mt-4">
60+
<MudItem xs="12">
61+
<MudLink Href="Account/ForgotPassword">Forgot your password?</MudLink><br />
62+
@if (_isPublicRegistrationEnabled)
5363
{
54-
<MudAlert Severity="Severity.Error" Variant="Variant.Text" Class="mt-4">
55-
@Input.Passkey.Error
56-
</MudAlert>
57-
}
64+
<MudLink
65+
Href="@(NavigationManager.GetUriWithQueryParameters("Account/Register", new Dictionary<string, object?> { ["ReturnUrl"] = ReturnUrl }))">
66+
Register as a new user</MudLink>
5867

59-
<MudGrid Class="mt-4">
60-
<MudItem xs="12">
61-
<MudLink Href="Account/ForgotPassword">Forgot your password?</MudLink><br />
62-
@if (_isPublicRegistrationEnabled)
63-
{
64-
<MudLink
65-
Href="@(NavigationManager.GetUriWithQueryParameters("Account/Register", new Dictionary<string, object?> { ["ReturnUrl"] = ReturnUrl }))">
66-
Register as a new user</MudLink>
67-
68-
<br />
69-
}
70-
<MudLink Href="Account/ResendEmailConfirmation">Resend email confirmation</MudLink>
71-
</MudItem>
72-
</MudGrid>
73-
</MudItem>
74-
<MudItem md="6">
75-
<MudText Typo="Typo.body1" Color="Color.Secondary" GutterBottom="true">
76-
Log in with an external provider.
77-
</MudText>
78-
<ExternalLoginPicker />
79-
</MudItem>
68+
<br />
69+
}
70+
<MudLink Href="Account/ResendEmailConfirmation">Resend email confirmation</MudLink>
71+
</MudItem>
72+
</MudGrid>
73+
</MudItem>
74+
<MudItem md="6">
75+
<MudText Typo="Typo.body1" Color="Color.Secondary" GutterBottom="true">
76+
Log in with an external provider.
77+
</MudText>
78+
<ExternalLoginPicker />
79+
</MudItem>
8080
</MudGrid>
8181

8282
@code {
83-
private string? _errorMessage;
84-
private bool _isPublicRegistrationEnabled;
85-
private EditContext _editContext = default!;
83+
private string? _errorMessage;
84+
private bool _isPublicRegistrationEnabled;
85+
private EditContext _editContext = default!;
8686

87-
[CascadingParameter]
88-
private HttpContext HttpContext { get; set; } = default!;
87+
[CascadingParameter]
88+
private HttpContext HttpContext { get; set; } = default!;
8989

90-
[SupplyParameterFromForm]
91-
private InputModel Input { get; set; } = default!;
90+
[SupplyParameterFromForm]
91+
private InputModel Input { get; set; } = default!;
9292

93-
[SupplyParameterFromQuery]
94-
private string? ReturnUrl { get; set; }
93+
[SupplyParameterFromQuery]
94+
private string? ReturnUrl { get; set; }
9595

96-
protected override async Task OnInitializedAsync()
97-
{
98-
Input ??= new InputModel();
99-
_editContext = new EditContext(Input);
96+
protected override async Task OnInitializedAsync()
97+
{
98+
Input ??= new InputModel();
99+
_editContext = new EditContext(Input);
100100

101-
if (HttpMethods.IsGet(HttpContext.Request.Method))
102-
{
103-
// Clear the existing external cookie to ensure a clean login process
104-
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
105-
}
106-
107-
var settingsResult = await ServerSettingsApi.GetPublicRegistrationSettings();
108-
if (settingsResult.IsSuccess)
109-
{
110-
_isPublicRegistrationEnabled = settingsResult.Value.IsPublicRegistrationEnabled;
111-
}
101+
if (HttpMethods.IsGet(HttpContext.Request.Method))
102+
{
103+
// Clear the existing external cookie to ensure a clean login process
104+
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
112105
}
113106

114-
public async Task LoginUser()
107+
var settingsResult = await ServerSettingsApi.GetPublicRegistrationSettings();
108+
if (settingsResult.IsSuccess)
115109
{
116-
if (!string.IsNullOrEmpty(Input.Passkey?.Error))
117-
{
118-
_errorMessage = $"Error: {Input.Passkey.Error}";
119-
return;
120-
}
110+
_isPublicRegistrationEnabled = settingsResult.Value.IsPublicRegistrationEnabled;
111+
}
112+
}
121113

122-
SignInResult result;
114+
public async Task LoginUser()
115+
{
116+
if (!string.IsNullOrEmpty(Input.Passkey?.Error))
117+
{
118+
_errorMessage = $"Error: {Input.Passkey.Error}";
119+
return;
120+
}
123121

124-
if (!string.IsNullOrEmpty(Input.Passkey?.CredentialJson))
125-
{
126-
result = await PasskeySignInManager.PasskeySignInAsync(Input.Passkey.CredentialJson);
127-
}
128-
else
129-
{
130-
if (!_editContext.Validate())
131-
{
132-
return;
133-
}
122+
SignInResult result;
123+
var passKeyCred = $"{Input.Passkey?.CredentialJson}";
124+
var isPassKeyLogin = !string.IsNullOrWhiteSpace(passKeyCred);
134125

135-
result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, true);
136-
}
126+
if (isPassKeyLogin)
127+
{
128+
result = await PasskeySignInManager.PasskeySignInAsync(passKeyCred);
129+
}
130+
else
131+
{
132+
if (!_editContext.Validate())
133+
{
134+
return;
135+
}
137136

138-
if (result.Succeeded)
139-
{
140-
Logger.LogInformation("User '{Email}' logged in.", Input.Email);
141-
RedirectManager.RedirectTo(ReturnUrl);
142-
}
143-
else if (result.RequiresTwoFactor)
144-
{
145-
RedirectManager.RedirectTo(
146-
"Account/LoginWith2fa",
147-
new Dictionary<string, object?> { ["returnUrl"] = ReturnUrl, ["rememberMe"] = Input.RememberMe });
148-
}
149-
else if (result.IsLockedOut)
150-
{
151-
Logger.LogCritical("User account locked out. Account: {Email}", Input.Email);
152-
RedirectManager.RedirectTo("Account/Lockout");
153-
}
154-
else
155-
{
156-
_errorMessage = "Error: Invalid login attempt.";
157-
}
137+
result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, true);
158138
}
159139

160-
private sealed class InputModel
140+
if (result.Succeeded)
141+
{
142+
if (isPassKeyLogin)
143+
{
144+
Logger.LogInformation("User logged in via passkey.");
145+
}
146+
else
147+
{
148+
Logger.LogInformation("User '{Email}' logged in with a password.", Input.Email);
149+
}
150+
151+
RedirectManager.RedirectTo(ReturnUrl);
152+
}
153+
else if (result.RequiresTwoFactor)
154+
{
155+
RedirectManager.RedirectTo(
156+
"Account/LoginWith2fa",
157+
new Dictionary<string, object?> { ["returnUrl"] = ReturnUrl, ["rememberMe"] = Input.RememberMe });
158+
}
159+
else if (result.IsLockedOut)
161160
{
162-
[Required]
163-
[EmailAddress]
164-
public string Email { get; set; } = "";
161+
if (isPassKeyLogin)
162+
{
163+
Logger.LogCritical("User account locked out via passkey login.");
164+
}
165+
else
166+
{
167+
Logger.LogCritical("User account locked out. Account: {Email}", Input.Email);
168+
}
169+
RedirectManager.RedirectTo("Account/Lockout");
170+
}
171+
else
172+
{
173+
_errorMessage = "Error: Invalid login attempt.";
174+
}
175+
}
165176

166-
[Required]
167-
[DataType(DataType.Password)]
168-
public string Password { get; set; } = "";
177+
private sealed class InputModel
178+
{
179+
[Required]
180+
[EmailAddress]
181+
public string Email { get; set; } = "";
169182

170-
[Display(Name = "Remember me?")]
171-
public bool RememberMe { get; set; }
183+
[Required]
184+
[DataType(DataType.Password)]
185+
public string Password { get; set; } = "";
172186

173-
public PasskeyInputModel? Passkey { get; set; }
174-
}
187+
[Display(Name = "Remember me?")]
188+
public bool RememberMe { get; set; }
189+
190+
public PasskeyInputModel? Passkey { get; set; }
191+
}
175192

176193
}

0 commit comments

Comments
 (0)