Skip to content

Commit 4b64ee2

Browse files
committed
Add metrics to passkey methods
1 parent 5a3b95d commit 4b64ee2

File tree

6 files changed

+87
-2
lines changed

6 files changed

+87
-2
lines changed

src/Identity/Core/src/SignInManager.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,22 @@ private void ThrowIfNoPasskeyHandler()
612612
/// for the sign-in attempt.
613613
/// </returns>
614614
public virtual async Task<SignInResult> PasskeySignInAsync(string credentialJson, PasskeyRequestOptions options)
615+
{
616+
try
617+
{
618+
var result = await PasskeySignInCoreAsync(credentialJson, options);
619+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Passkey, isPersistent: false);
620+
621+
return result;
622+
}
623+
catch (Exception ex)
624+
{
625+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Passkey, isPersistent: false, ex);
626+
throw;
627+
}
628+
}
629+
630+
private async Task<SignInResult> PasskeySignInCoreAsync(string credentialJson, PasskeyRequestOptions options)
615631
{
616632
ArgumentException.ThrowIfNullOrEmpty(credentialJson);
617633

src/Identity/Core/src/SignInManagerMetrics.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ private static string GetSignInType(SignInType signInType)
165165
SignInType.TwoFactorAuthenticator => "two_factor_authenticator",
166166
SignInType.TwoFactor => "two_factor",
167167
SignInType.External => "external",
168+
SignInType.Passkey => "passkey",
168169
_ => "_UNKNOWN"
169170
};
170171
}
@@ -188,5 +189,6 @@ internal enum SignInType
188189
TwoFactorRecoveryCode,
189190
TwoFactorAuthenticator,
190191
TwoFactor,
191-
External
192+
External,
193+
Passkey
192194
}

src/Identity/Extensions.Core/src/UserManager.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2558,6 +2558,21 @@ public virtual Task<int> CountRecoveryCodesAsync(TUser user)
25582558
/// <param name="passkey">The passkey to add or update.</param>
25592559
/// <returns>Whether the passkey was successfully set.</returns>
25602560
public virtual async Task<IdentityResult> SetPasskeyAsync(TUser user, UserPasskeyInfo passkey)
2561+
{
2562+
try
2563+
{
2564+
var result = await SetPasskeyCoreAsync(user, passkey).ConfigureAwait(false);
2565+
_metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.SetPasskey);
2566+
return result;
2567+
}
2568+
catch (Exception ex)
2569+
{
2570+
_metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetPasskey, ex);
2571+
throw;
2572+
}
2573+
}
2574+
2575+
private async Task<IdentityResult> SetPasskeyCoreAsync(TUser user, UserPasskeyInfo passkey)
25612576
{
25622577
ThrowIfDisposed();
25632578
var passkeyStore = GetUserPasskeyStore();
@@ -2628,6 +2643,21 @@ public virtual Task<IList<UserPasskeyInfo>> GetPasskeysAsync(TUser user)
26282643
/// of the operation.
26292644
/// </returns>
26302645
public virtual async Task<IdentityResult> RemovePasskeyAsync(TUser user, byte[] credentialId)
2646+
{
2647+
try
2648+
{
2649+
var result = await RemovePasskeyCoreAsync(user, credentialId).ConfigureAwait(false);
2650+
_metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.RemovePasskey);
2651+
return result;
2652+
}
2653+
catch (Exception ex)
2654+
{
2655+
_metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemovePasskey, ex);
2656+
throw;
2657+
}
2658+
}
2659+
2660+
private async Task<IdentityResult> RemovePasskeyCoreAsync(TUser user, byte[] credentialId)
26312661
{
26322662
ThrowIfDisposed();
26332663
var passkeyStore = GetUserPasskeyStore();

src/Identity/Extensions.Core/src/UserManagerMetrics.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@ private static string GetUpdateType(UserUpdateType updateType)
239239
UserUpdateType.SetAuthenticationToken => "set_authentication_token",
240240
UserUpdateType.RemoveAuthenticationToken => "remove_authentication_token",
241241
UserUpdateType.ResetAuthenticatorKey => "reset_authenticator_key",
242+
UserUpdateType.GenerateNewTwoFactorRecoveryCodes => "generate_new_two_factor_recovery_codes",
243+
UserUpdateType.RedeemTwoFactorRecoveryCode => "redeem_two_factor_recovery_code",
244+
UserUpdateType.SetPasskey => "set_passkey",
245+
UserUpdateType.RemovePasskey => "remove_passkey",
242246
_ => "_UNKNOWN"
243247
};
244248
}
@@ -281,4 +285,6 @@ internal enum UserUpdateType
281285
ResetAuthenticatorKey,
282286
GenerateNewTwoFactorRecoveryCodes,
283287
RedeemTwoFactorRecoveryCode,
288+
SetPasskey,
289+
RemovePasskey
284290
}

src/Identity/Identity.slnf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
"path": "..\\..\\AspNetCore.slnx",
44
"projects": [
55
"src\\Antiforgery\\src\\Microsoft.AspNetCore.Antiforgery.csproj",
6+
"src\\Components\\Authorization\\src\\Microsoft.AspNetCore.Components.Authorization.csproj",
67
"src\\Components\\Components\\src\\Microsoft.AspNetCore.Components.csproj",
8+
"src\\Components\\Endpoints\\src\\Microsoft.AspNetCore.Components.Endpoints.csproj",
9+
"src\\Components\\Forms\\src\\Microsoft.AspNetCore.Components.Forms.csproj",
710
"src\\Components\\Web\\src\\Microsoft.AspNetCore.Components.Web.csproj",
811
"src\\DataProtection\\Abstractions\\src\\Microsoft.AspNetCore.DataProtection.Abstractions.csproj",
912
"src\\DataProtection\\Cryptography.Internal\\src\\Microsoft.AspNetCore.Cryptography.Internal.csproj",
@@ -13,6 +16,8 @@
1316
"src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj",
1417
"src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj",
1518
"src\\Features\\JsonPatch\\src\\Microsoft.AspNetCore.JsonPatch.csproj",
19+
"src\\FileProviders\\Embedded\\src\\Microsoft.Extensions.FileProviders.Embedded.csproj",
20+
"src\\FileProviders\\Manifest.MSBuildTask\\src\\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj",
1621
"src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj",
1722
"src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
1823
"src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj",
@@ -48,7 +53,9 @@
4853
"src\\Identity\\test\\Identity.Test\\Microsoft.AspNetCore.Identity.Test.csproj",
4954
"src\\Identity\\test\\InMemory.Test\\Microsoft.AspNetCore.Identity.InMemory.Test.csproj",
5055
"src\\Identity\\testassets\\Identity.DefaultUI.WebSite\\Identity.DefaultUI.WebSite.csproj",
56+
"src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj",
5157
"src\\Localization\\Abstractions\\src\\Microsoft.Extensions.Localization.Abstractions.csproj",
58+
"src\\Localization\\Localization\\src\\Microsoft.Extensions.Localization.csproj",
5259
"src\\Middleware\\CORS\\src\\Microsoft.AspNetCore.Cors.csproj",
5360
"src\\Middleware\\Diagnostics.Abstractions\\src\\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj",
5461
"src\\Middleware\\Diagnostics.EntityFrameworkCore\\src\\Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj",
@@ -57,6 +64,7 @@
5764
"src\\Middleware\\HttpOverrides\\src\\Microsoft.AspNetCore.HttpOverrides.csproj",
5865
"src\\Middleware\\HttpsPolicy\\src\\Microsoft.AspNetCore.HttpsPolicy.csproj",
5966
"src\\Middleware\\Localization\\src\\Microsoft.AspNetCore.Localization.csproj",
67+
"src\\Middleware\\OutputCaching\\src\\Microsoft.AspNetCore.OutputCaching.csproj",
6068
"src\\Middleware\\ResponseCaching.Abstractions\\src\\Microsoft.AspNetCore.ResponseCaching.Abstractions.csproj",
6169
"src\\Middleware\\Rewrite\\src\\Microsoft.AspNetCore.Rewrite.csproj",
6270
"src\\Middleware\\StaticFiles\\src\\Microsoft.AspNetCore.StaticFiles.csproj",
@@ -66,6 +74,7 @@
6674
"src\\Mvc\\Mvc.Core\\src\\Microsoft.AspNetCore.Mvc.Core.csproj",
6775
"src\\Mvc\\Mvc.Cors\\src\\Microsoft.AspNetCore.Mvc.Cors.csproj",
6876
"src\\Mvc\\Mvc.DataAnnotations\\src\\Microsoft.AspNetCore.Mvc.DataAnnotations.csproj",
77+
"src\\Mvc\\Mvc.Formatters.Json\\src\\Microsoft.AspNetCore.Mvc.Formatters.Json.csproj",
6978
"src\\Mvc\\Mvc.Localization\\src\\Microsoft.AspNetCore.Mvc.Localization.csproj",
7079
"src\\Mvc\\Mvc.NewtonsoftJson\\src\\Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj",
7180
"src\\Mvc\\Mvc.RazorPages\\src\\Microsoft.AspNetCore.Mvc.RazorPages.csproj",
@@ -102,7 +111,9 @@
102111
"src\\Servers\\Kestrel\\Transport.NamedPipes\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj",
103112
"src\\Servers\\Kestrel\\Transport.Quic\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj",
104113
"src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj",
114+
"src\\StaticAssets\\src\\Microsoft.AspNetCore.StaticAssets.csproj",
105115
"src\\Testing\\src\\Microsoft.AspNetCore.InternalTesting.csproj",
116+
"src\\Validation\\src\\Microsoft.Extensions.Validation.csproj",
106117
"src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj"
107118
]
108119
}

src/Identity/test/Identity.Test/SignInManagerTest.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,14 +426,18 @@ public async Task ExternalSignInRequiresVerificationIfNotBypassed(bool bypass)
426426
public async Task CanPasskeySignIn()
427427
{
428428
// Setup
429+
var testMeterFactory = new TestMeterFactory();
430+
using var authenticate = new MetricCollector<long>(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateCounterName);
431+
using var signInUserPrincipal = new MetricCollector<long>(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignInUserPrincipalCounterName);
432+
429433
var user = new PocoUser { UserName = "Foo" };
430434
var passkey = new UserPasskeyInfo(null, null, null, default, 0, null, false, false, false, null, null);
431435
var assertionResult = PasskeyAssertionResult.Success(passkey, user);
432436
var passkeyHandler = new Mock<IPasskeyHandler<PocoUser>>();
433437
passkeyHandler
434438
.Setup(h => h.PerformAssertionAsync(It.IsAny<PasskeyAssertionContext<PocoUser>>()))
435439
.Returns(Task.FromResult(assertionResult));
436-
var manager = SetupUserManager(user);
440+
var manager = SetupUserManager(user, meterFactory: testMeterFactory);
437441
manager
438442
.Setup(m => m.SetPasskeyAsync(user, passkey))
439443
.Returns(Task.FromResult(IdentityResult.Success))
@@ -452,6 +456,22 @@ public async Task CanPasskeySignIn()
452456
Assert.Same(SignInResult.Success, signInResult);
453457
manager.Verify();
454458
auth.Verify();
459+
460+
Assert.Collection(authenticate.GetMeasurementSnapshot(),
461+
m => MetricsHelpers.AssertContainsTags(m.Tags,
462+
[
463+
KeyValuePair.Create<string, object>("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"),
464+
KeyValuePair.Create<string, object>("aspnetcore.identity.authentication_scheme", "Identity.Application"),
465+
KeyValuePair.Create<string, object>("aspnetcore.identity.sign_in.type", "passkey"),
466+
KeyValuePair.Create<string, object>("aspnetcore.identity.sign_in.is_persistent", false),
467+
KeyValuePair.Create<string, object>("aspnetcore.identity.sign_in.result", "success"),
468+
]));
469+
Assert.Collection(signInUserPrincipal.GetMeasurementSnapshot(),
470+
m => MetricsHelpers.AssertContainsTags(m.Tags,
471+
[
472+
KeyValuePair.Create<string, object>("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"),
473+
KeyValuePair.Create<string, object>("aspnetcore.identity.authentication_scheme", "Identity.Application"),
474+
]));
455475
}
456476

457477
private class GoodTokenProvider : AuthenticatorTokenProvider<PocoUser>

0 commit comments

Comments
 (0)