Skip to content

Commit e9d6075

Browse files
authored
Update identity metrics with feedback (#62982)
1 parent 0e8cbde commit e9d6075

File tree

12 files changed

+405
-265
lines changed

12 files changed

+405
-265
lines changed

src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<ItemGroup>
1616
<Compile Include="$(SharedSourceRoot)DefaultMessageEmailSender.cs" LinkBase="Shared" />
1717
<Compile Include="$(SharedSourceRoot)Metrics\MetricsConstants.cs" LinkBase="Shared" />
18+
<Compile Include="$(SharedSourceRoot)ValueStopwatch\ValueStopwatch.cs" LinkBase="Shared" />
1819
</ItemGroup>
1920

2021
<ItemGroup>

src/Identity/Core/src/SignInManager.cs

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
45
using System.Diagnostics.CodeAnalysis;
56
using System.Linq;
67
using System.Security.Claims;
@@ -169,15 +170,16 @@ public virtual async Task<bool> CanSignInAsync(TUser user)
169170
/// <returns>The task object representing the asynchronous operation.</returns>
170171
public virtual async Task RefreshSignInAsync(TUser user)
171172
{
173+
var startTimestamp = Stopwatch.GetTimestamp();
172174
try
173175
{
174176
var (success, isPersistent) = await RefreshSignInCoreAsync(user);
175177
var signInResult = success ? SignInResult.Success : SignInResult.Failed;
176-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, signInResult, SignInType.Refresh, isPersistent);
178+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, signInResult, SignInType.Refresh, isPersistent, startTimestamp);
177179
}
178180
catch (Exception ex)
179181
{
180-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Refresh, isPersistent: null, ex);
182+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Refresh, isPersistent: null, startTimestamp, ex);
181183
throw;
182184
}
183185
}
@@ -391,6 +393,7 @@ public virtual async Task<bool> ValidateSecurityStampAsync(TUser? user, string?
391393
public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string password,
392394
bool isPersistent, bool lockoutOnFailure)
393395
{
396+
var startTimestamp = Stopwatch.GetTimestamp();
394397
try
395398
{
396399
ArgumentNullException.ThrowIfNull(user);
@@ -399,13 +402,13 @@ public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string p
399402
var result = attempt.Succeeded
400403
? await SignInOrTwoFactorAsync(user, isPersistent)
401404
: attempt;
402-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Password, isPersistent);
405+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Password, isPersistent, startTimestamp);
403406

404407
return result;
405408
}
406409
catch (Exception ex)
407410
{
408-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Password, isPersistent, ex);
411+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Password, isPersistent, startTimestamp, ex);
409412
throw;
410413
}
411414
}
@@ -423,10 +426,11 @@ public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string p
423426
public virtual async Task<SignInResult> PasswordSignInAsync(string userName, string password,
424427
bool isPersistent, bool lockoutOnFailure)
425428
{
429+
var startTimestamp = Stopwatch.GetTimestamp();
426430
var user = await UserManager.FindByNameAsync(userName);
427431
if (user == null)
428432
{
429-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, SignInResult.Failed, SignInType.Password, isPersistent);
433+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, SignInResult.Failed, SignInType.Password, isPersistent, startTimestamp);
430434
return SignInResult.Failed;
431435
}
432436

@@ -635,16 +639,17 @@ public virtual async Task<PasskeyAssertionResult<TUser>> PerformPasskeyAssertion
635639
/// </returns>
636640
public virtual async Task<SignInResult> PasskeySignInAsync(string credentialJson)
637641
{
642+
var startTimestamp = Stopwatch.GetTimestamp();
638643
try
639644
{
640645
var result = await PasskeySignInCoreAsync(credentialJson);
641-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Passkey, isPersistent: false);
646+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Passkey, isPersistent: false, startTimestamp);
642647

643648
return result;
644649
}
645650
catch (Exception ex)
646651
{
647-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Passkey, isPersistent: false, ex);
652+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Passkey, isPersistent: false, startTimestamp, ex);
648653
throw;
649654
}
650655
}
@@ -787,16 +792,17 @@ public virtual async Task ForgetTwoFactorClientAsync()
787792
/// <returns></returns>
788793
public virtual async Task<SignInResult> TwoFactorRecoveryCodeSignInAsync(string recoveryCode)
789794
{
795+
var startTimestamp = Stopwatch.GetTimestamp();
790796
try
791797
{
792798
var result = await TwoFactorRecoveryCodeSignInCoreAsync(recoveryCode);
793-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorRecoveryCode, isPersistent: false);
799+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorRecoveryCode, isPersistent: false, startTimestamp);
794800

795801
return result;
796802
}
797803
catch (Exception ex)
798804
{
799-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorRecoveryCode, isPersistent: false, ex);
805+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorRecoveryCode, isPersistent: false, startTimestamp, ex);
800806
throw;
801807
}
802808
}
@@ -868,16 +874,17 @@ private async Task<SignInResult> DoTwoFactorSignInAsync(TUser user, TwoFactorAut
868874
/// for the sign-in attempt.</returns>
869875
public virtual async Task<SignInResult> TwoFactorAuthenticatorSignInAsync(string code, bool isPersistent, bool rememberClient)
870876
{
877+
var startTimestamp = Stopwatch.GetTimestamp();
871878
try
872879
{
873880
var result = await TwoFactorAuthenticatorSignInCoreAsync(code, isPersistent, rememberClient);
874-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorAuthenticator, isPersistent);
881+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorAuthenticator, isPersistent, startTimestamp);
875882

876883
return result;
877884
}
878885
catch (Exception ex)
879886
{
880-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorAuthenticator, isPersistent, ex);
887+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorAuthenticator, isPersistent, startTimestamp, ex);
881888
throw;
882889
}
883890
}
@@ -932,16 +939,17 @@ private async Task<SignInResult> TwoFactorAuthenticatorSignInCoreAsync(string co
932939
/// for the sign-in attempt.</returns>
933940
public virtual async Task<SignInResult> TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient)
934941
{
942+
var startTimestamp = Stopwatch.GetTimestamp();
935943
try
936944
{
937945
var result = await TwoFactorSignInCoreAsync(provider, code, isPersistent, rememberClient);
938-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactor, isPersistent);
946+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactor, isPersistent, startTimestamp);
939947

940948
return result;
941949
}
942950
catch (Exception ex)
943951
{
944-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactor, isPersistent, ex);
952+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactor, isPersistent, startTimestamp, ex);
945953
throw;
946954
}
947955
}
@@ -1021,16 +1029,17 @@ public virtual Task<SignInResult> ExternalLoginSignInAsync(string loginProvider,
10211029
/// for the sign-in attempt.</returns>
10221030
public virtual async Task<SignInResult> ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, bool bypassTwoFactor)
10231031
{
1032+
var startTimestamp = Stopwatch.GetTimestamp();
10241033
try
10251034
{
10261035
var result = await ExternalLoginSignInCoreAsync(loginProvider, providerKey, isPersistent, bypassTwoFactor);
1027-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.External, isPersistent);
1036+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.External, isPersistent, startTimestamp);
10281037

10291038
return result;
10301039
}
10311040
catch (Exception ex)
10321041
{
1033-
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.External, isPersistent, ex);
1042+
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.External, isPersistent, startTimestamp, ex);
10341043
throw;
10351044
}
10361045
}

src/Identity/Core/src/SignInManagerMetrics.cs

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,64 @@
33

44
using System.Diagnostics;
55
using System.Diagnostics.Metrics;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.Extensions.Internal;
68

79
namespace Microsoft.AspNetCore.Identity;
810

911
internal sealed class SignInManagerMetrics : IDisposable
1012
{
1113
public const string MeterName = "Microsoft.AspNetCore.Identity";
1214

13-
public const string AuthenticateCounterName = "aspnetcore.identity.sign_in.authenticate";
14-
public const string RememberTwoFactorCounterName = "aspnetcore.identity.sign_in.remember_two_factor";
15-
public const string ForgetTwoFactorCounterName = "aspnetcore.identity.sign_in.forget_two_factor";
16-
public const string CheckPasswordCounterName = "aspnetcore.identity.sign_in.check_password";
17-
public const string SignInUserPrincipalCounterName = "aspnetcore.identity.sign_in.sign_in";
18-
public const string SignOutUserPrincipalCounterName = "aspnetcore.identity.sign_in.sign_out";
15+
public const string AuthenticateDurationName = "aspnetcore.identity.sign_in.authenticate.duration";
16+
public const string RememberedTwoFactorCounterName = "aspnetcore.identity.sign_in.two_factor_clients_remembered";
17+
public const string ForgottenTwoFactorCounterName = "aspnetcore.identity.sign_in.two_factor_clients_forgotten";
18+
public const string CheckPasswordAttemptsCounterName = "aspnetcore.identity.sign_in.check_password_attempts";
19+
public const string SignInsCounterName = "aspnetcore.identity.sign_in.sign_ins";
20+
public const string SignOutsCounterName = "aspnetcore.identity.sign_in.sign_outs";
1921

2022
private readonly Meter _meter;
21-
private readonly Counter<long> _authenticateCounter;
23+
private readonly Histogram<double> _authenticateDuration;
2224
private readonly Counter<long> _rememberTwoFactorClientCounter;
2325
private readonly Counter<long> _forgetTwoFactorCounter;
2426
private readonly Counter<long> _checkPasswordCounter;
25-
private readonly Counter<long> _signInUserPrincipalCounter;
26-
private readonly Counter<long> _signOutUserPrincipalCounter;
27+
private readonly Counter<long> _signInsCounter;
28+
private readonly Counter<long> _signOutsCounter;
2729

2830
public SignInManagerMetrics(IMeterFactory meterFactory)
2931
{
3032
_meter = meterFactory.Create(MeterName);
3133

32-
_authenticateCounter = _meter.CreateCounter<long>(AuthenticateCounterName, "{count}", "The number of authenticate attempts. The authenticate counter is incremented by sign in methods such as PasswordSignInAsync and TwoFactorSignInAsync.");
33-
_rememberTwoFactorClientCounter = _meter.CreateCounter<long>(RememberTwoFactorCounterName, "{count}", "The number of two factor clients remembered.");
34-
_forgetTwoFactorCounter = _meter.CreateCounter<long>(ForgetTwoFactorCounterName, "{count}", "The number of two factor clients forgotten.");
35-
_checkPasswordCounter = _meter.CreateCounter<long>(CheckPasswordCounterName, "{check}", "The number of check password attempts. Checks that the account is in a state that can log in and that the password is valid using the UserManager.CheckPasswordAsync method.");
36-
_signInUserPrincipalCounter = _meter.CreateCounter<long>(SignInUserPrincipalCounterName, "{sign_in}", "The number of calls to sign in user principals.");
37-
_signOutUserPrincipalCounter = _meter.CreateCounter<long>(SignOutUserPrincipalCounterName, "{sign_out}", "The number of calls to sign out user principals.");
34+
_authenticateDuration = _meter.CreateHistogram<double>(
35+
AuthenticateDurationName,
36+
unit: "s",
37+
description: "The duration of authenticate attempts. The authenticate metrics is recorded by sign in methods such as PasswordSignInAsync and TwoFactorSignInAsync.",
38+
advice: new() { HistogramBucketBoundaries = MetricsConstants.ShortSecondsBucketBoundaries });
39+
40+
_rememberTwoFactorClientCounter = _meter.CreateCounter<long>(
41+
RememberedTwoFactorCounterName,
42+
unit: "{client}",
43+
description: "The total number of two factor clients remembered.");
44+
45+
_forgetTwoFactorCounter = _meter.CreateCounter<long>(
46+
ForgottenTwoFactorCounterName,
47+
unit: "{client}",
48+
description: "The total number of two factor clients forgotten.");
49+
50+
_checkPasswordCounter = _meter.CreateCounter<long>(
51+
CheckPasswordAttemptsCounterName,
52+
unit: "{attempt}",
53+
description: "The total number of check password attempts. Checks that the account is in a state that can log in and that the password is valid using the UserManager.CheckPasswordAsync method.");
54+
55+
_signInsCounter = _meter.CreateCounter<long>(
56+
SignInsCounterName,
57+
unit: "{sign_in}",
58+
description: "The total number of calls to sign in user principals.");
59+
60+
_signOutsCounter = _meter.CreateCounter<long>(
61+
SignOutsCounterName,
62+
unit: "{sign_out}",
63+
description: "The total number of calls to sign out user principals.");
3864
}
3965

4066
internal void CheckPasswordSignIn(string userType, SignInResult? result, Exception? exception = null)
@@ -54,59 +80,60 @@ internal void CheckPasswordSignIn(string userType, SignInResult? result, Excepti
5480
_checkPasswordCounter.Add(1, tags);
5581
}
5682

57-
internal void AuthenticateSignIn(string userType, string authenticationScheme, SignInResult? result, SignInType signInType, bool? isPersistent, Exception? exception = null)
83+
internal void AuthenticateSignIn(string userType, string authenticationScheme, SignInResult? result, SignInType signInType, bool? isPersistent, long startTimestamp, Exception? exception = null)
5884
{
59-
if (!_authenticateCounter.Enabled)
85+
if (!_authenticateDuration.Enabled)
6086
{
6187
return;
6288
}
6389

6490
var tags = new TagList
6591
{
6692
{ "aspnetcore.identity.user_type", userType },
67-
{ "aspnetcore.identity.authentication_scheme", authenticationScheme },
93+
{ "aspnetcore.authentication.scheme", authenticationScheme },
6894
{ "aspnetcore.identity.sign_in.type", GetSignInType(signInType) },
6995
};
7096
AddIsPersistent(ref tags, isPersistent);
7197
AddSignInResult(ref tags, result);
7298
AddErrorTag(ref tags, exception);
7399

74-
_authenticateCounter.Add(1, tags);
100+
var duration = ValueStopwatch.GetElapsedTime(startTimestamp, Stopwatch.GetTimestamp());
101+
_authenticateDuration.Record(duration.TotalSeconds, tags);
75102
}
76103

77104
internal void SignInUserPrincipal(string userType, string authenticationScheme, bool? isPersistent, Exception? exception = null)
78105
{
79-
if (!_signInUserPrincipalCounter.Enabled)
106+
if (!_signInsCounter.Enabled)
80107
{
81108
return;
82109
}
83110

84111
var tags = new TagList
85112
{
86113
{ "aspnetcore.identity.user_type", userType },
87-
{ "aspnetcore.identity.authentication_scheme", authenticationScheme },
114+
{ "aspnetcore.authentication.scheme", authenticationScheme },
88115
};
89116
AddIsPersistent(ref tags, isPersistent);
90117
AddErrorTag(ref tags, exception);
91118

92-
_signInUserPrincipalCounter.Add(1, tags);
119+
_signInsCounter.Add(1, tags);
93120
}
94121

95122
internal void SignOutUserPrincipal(string userType, string authenticationScheme, Exception? exception = null)
96123
{
97-
if (!_signOutUserPrincipalCounter.Enabled)
124+
if (!_signOutsCounter.Enabled)
98125
{
99126
return;
100127
}
101128

102129
var tags = new TagList
103130
{
104131
{ "aspnetcore.identity.user_type", userType },
105-
{ "aspnetcore.identity.authentication_scheme", authenticationScheme },
132+
{ "aspnetcore.authentication.scheme", authenticationScheme },
106133
};
107134
AddErrorTag(ref tags, exception);
108135

109-
_signOutUserPrincipalCounter.Add(1, tags);
136+
_signOutsCounter.Add(1, tags);
110137
}
111138

112139
internal void RememberTwoFactorClient(string userType, string authenticationScheme, Exception? exception = null)
@@ -119,7 +146,7 @@ internal void RememberTwoFactorClient(string userType, string authenticationSche
119146
var tags = new TagList
120147
{
121148
{ "aspnetcore.identity.user_type", userType },
122-
{ "aspnetcore.identity.authentication_scheme", authenticationScheme }
149+
{ "aspnetcore.authentication.scheme", authenticationScheme }
123150
};
124151
AddErrorTag(ref tags, exception);
125152

@@ -136,7 +163,7 @@ internal void ForgetTwoFactorClient(string userType, string authenticationScheme
136163
var tags = new TagList
137164
{
138165
{ "aspnetcore.identity.user_type", userType },
139-
{ "aspnetcore.identity.authentication_scheme", authenticationScheme }
166+
{ "aspnetcore.authentication.scheme", authenticationScheme }
140167
};
141168
AddErrorTag(ref tags, exception);
142169

@@ -152,7 +179,7 @@ private static void AddIsPersistent(ref TagList tags, bool? isPersistent)
152179
{
153180
if (isPersistent != null)
154181
{
155-
tags.Add("aspnetcore.identity.sign_in.is_persistent", isPersistent.Value);
182+
tags.Add("aspnetcore.authentication.is_persistent", isPersistent.Value);
156183
}
157184
}
158185

src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
<Reference Include="Microsoft.Extensions.Logging" />
1616
<Reference Include="Microsoft.Extensions.Options" />
1717

18+
<Compile Include="$(SharedSourceRoot)Metrics\MetricsConstants.cs" LinkBase="Shared" />
1819
<Compile Include="$(SharedSourceRoot)TrimmingAttributes.cs" LinkBase="Shared" />
20+
<Compile Include="$(SharedSourceRoot)ValueStopwatch\ValueStopwatch.cs" LinkBase="Shared" />
1921
<Compile Include="$(SharedSourceRoot)ThrowHelpers\ArgumentNullThrowHelper.cs" LinkBase="Shared" />
2022
<Compile Include="$(SharedSourceRoot)ThrowHelpers\ArgumentOutOfRangeThrowHelper.cs" LinkBase="Shared" />
2123
<Compile Include="$(SharedSourceRoot)ThrowHelpers\ObjectDisposedThrowHelper.cs" LinkBase="Shared" />

0 commit comments

Comments
 (0)