Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<ItemGroup>
<Compile Include="$(SharedSourceRoot)DefaultMessageEmailSender.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)Metrics\MetricsConstants.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)ValueStopwatch\ValueStopwatch.cs" LinkBase="Shared" />
</ItemGroup>

<ItemGroup>
Expand Down
39 changes: 24 additions & 15 deletions src/Identity/Core/src/SignInManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Claims;
Expand Down Expand Up @@ -169,15 +170,16 @@ public virtual async Task<bool> CanSignInAsync(TUser user)
/// <returns>The task object representing the asynchronous operation.</returns>
public virtual async Task RefreshSignInAsync(TUser user)
{
var startTimestamp = Stopwatch.GetTimestamp();
try
{
var (success, isPersistent) = await RefreshSignInCoreAsync(user);
var signInResult = success ? SignInResult.Success : SignInResult.Failed;
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, signInResult, SignInType.Refresh, isPersistent);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, signInResult, SignInType.Refresh, isPersistent, startTimestamp);
}
catch (Exception ex)
{
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Refresh, isPersistent: null, ex);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Refresh, isPersistent: null, startTimestamp, ex);
throw;
}
}
Expand Down Expand Up @@ -391,6 +393,7 @@ public virtual async Task<bool> ValidateSecurityStampAsync(TUser? user, string?
public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string password,
bool isPersistent, bool lockoutOnFailure)
{
var startTimestamp = Stopwatch.GetTimestamp();
try
{
ArgumentNullException.ThrowIfNull(user);
Expand All @@ -399,13 +402,13 @@ public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string p
var result = attempt.Succeeded
? await SignInOrTwoFactorAsync(user, isPersistent)
: attempt;
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Password, isPersistent);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Password, isPersistent, startTimestamp);

return result;
}
catch (Exception ex)
{
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Password, isPersistent, ex);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Password, isPersistent, startTimestamp, ex);
throw;
}
}
Expand All @@ -423,10 +426,11 @@ public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string p
public virtual async Task<SignInResult> PasswordSignInAsync(string userName, string password,
bool isPersistent, bool lockoutOnFailure)
{
var startTimestamp = Stopwatch.GetTimestamp();
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
{
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, SignInResult.Failed, SignInType.Password, isPersistent);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, SignInResult.Failed, SignInType.Password, isPersistent, startTimestamp);
return SignInResult.Failed;
}

Expand Down Expand Up @@ -635,16 +639,17 @@ public virtual async Task<PasskeyAssertionResult<TUser>> PerformPasskeyAssertion
/// </returns>
public virtual async Task<SignInResult> PasskeySignInAsync(string credentialJson)
{
var startTimestamp = Stopwatch.GetTimestamp();
try
{
var result = await PasskeySignInCoreAsync(credentialJson);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Passkey, isPersistent: false);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Passkey, isPersistent: false, startTimestamp);

return result;
}
catch (Exception ex)
{
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Passkey, isPersistent: false, ex);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Passkey, isPersistent: false, startTimestamp, ex);
throw;
}
}
Expand Down Expand Up @@ -787,16 +792,17 @@ public virtual async Task ForgetTwoFactorClientAsync()
/// <returns></returns>
public virtual async Task<SignInResult> TwoFactorRecoveryCodeSignInAsync(string recoveryCode)
{
var startTimestamp = Stopwatch.GetTimestamp();
try
{
var result = await TwoFactorRecoveryCodeSignInCoreAsync(recoveryCode);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorRecoveryCode, isPersistent: false);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorRecoveryCode, isPersistent: false, startTimestamp);

return result;
}
catch (Exception ex)
{
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorRecoveryCode, isPersistent: false, ex);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorRecoveryCode, isPersistent: false, startTimestamp, ex);
throw;
}
}
Expand Down Expand Up @@ -868,16 +874,17 @@ private async Task<SignInResult> DoTwoFactorSignInAsync(TUser user, TwoFactorAut
/// for the sign-in attempt.</returns>
public virtual async Task<SignInResult> TwoFactorAuthenticatorSignInAsync(string code, bool isPersistent, bool rememberClient)
{
var startTimestamp = Stopwatch.GetTimestamp();
try
{
var result = await TwoFactorAuthenticatorSignInCoreAsync(code, isPersistent, rememberClient);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorAuthenticator, isPersistent);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorAuthenticator, isPersistent, startTimestamp);

return result;
}
catch (Exception ex)
{
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorAuthenticator, isPersistent, ex);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorAuthenticator, isPersistent, startTimestamp, ex);
throw;
}
}
Expand Down Expand Up @@ -932,16 +939,17 @@ private async Task<SignInResult> TwoFactorAuthenticatorSignInCoreAsync(string co
/// for the sign-in attempt.</returns>
public virtual async Task<SignInResult> TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient)
{
var startTimestamp = Stopwatch.GetTimestamp();
try
{
var result = await TwoFactorSignInCoreAsync(provider, code, isPersistent, rememberClient);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactor, isPersistent);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactor, isPersistent, startTimestamp);

return result;
}
catch (Exception ex)
{
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactor, isPersistent, ex);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactor, isPersistent, startTimestamp, ex);
throw;
}
}
Expand Down Expand Up @@ -1021,16 +1029,17 @@ public virtual Task<SignInResult> ExternalLoginSignInAsync(string loginProvider,
/// for the sign-in attempt.</returns>
public virtual async Task<SignInResult> ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, bool bypassTwoFactor)
{
var startTimestamp = Stopwatch.GetTimestamp();
try
{
var result = await ExternalLoginSignInCoreAsync(loginProvider, providerKey, isPersistent, bypassTwoFactor);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.External, isPersistent);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.External, isPersistent, startTimestamp);

return result;
}
catch (Exception ex)
{
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.External, isPersistent, ex);
_metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.External, isPersistent, startTimestamp, ex);
throw;
}
}
Expand Down
46 changes: 24 additions & 22 deletions src/Identity/Core/src/SignInManagerMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,39 @@

using System.Diagnostics;
using System.Diagnostics.Metrics;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Identity;

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

public const string AuthenticateCounterName = "aspnetcore.identity.sign_in.authenticate";
public const string RememberTwoFactorCounterName = "aspnetcore.identity.sign_in.remember_two_factor";
public const string ForgetTwoFactorCounterName = "aspnetcore.identity.sign_in.forget_two_factor";
public const string CheckPasswordCounterName = "aspnetcore.identity.sign_in.check_password";
public const string SignInUserPrincipalCounterName = "aspnetcore.identity.sign_in.sign_in";
public const string SignOutUserPrincipalCounterName = "aspnetcore.identity.sign_in.sign_out";
public const string AuthenticateDurationName = "aspnetcore.identity.sign_in.authenticate.duration";
public const string RememberedTwoFactorCounterName = "aspnetcore.identity.sign_in.remembered_two_factor_clients";
public const string ForgottenTwoFactorCounterName = "aspnetcore.identity.sign_in.forgotten_two_factor_clients";
public const string CheckPasswordAttemptsCounterName = "aspnetcore.identity.sign_in.check_password_attempts";
public const string SignInsCounterName = "aspnetcore.identity.sign_in.sign_ins";
public const string SignOutsCounterName = "aspnetcore.identity.sign_in.sign_outs";

private readonly Meter _meter;
private readonly Counter<long> _authenticateCounter;
private readonly Histogram<double> _authenticateDuration;
private readonly Counter<long> _rememberTwoFactorClientCounter;
private readonly Counter<long> _forgetTwoFactorCounter;
private readonly Counter<long> _checkPasswordCounter;
private readonly Counter<long> _signInUserPrincipalCounter;
private readonly Counter<long> _signOutUserPrincipalCounter;
private readonly Counter<long> _signInsCounter;
private readonly Counter<long> _signOutsCounter;

public SignInManagerMetrics(IMeterFactory meterFactory)
{
_meter = meterFactory.Create(MeterName);

_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.");
_rememberTwoFactorClientCounter = _meter.CreateCounter<long>(RememberTwoFactorCounterName, "{count}", "The number of two factor clients remembered.");
_forgetTwoFactorCounter = _meter.CreateCounter<long>(ForgetTwoFactorCounterName, "{count}", "The number of two factor clients forgotten.");
_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.");
_signInUserPrincipalCounter = _meter.CreateCounter<long>(SignInUserPrincipalCounterName, "{sign_in}", "The number of calls to sign in user principals.");
_signOutUserPrincipalCounter = _meter.CreateCounter<long>(SignOutUserPrincipalCounterName, "{sign_out}", "The number of calls to sign out user principals.");
_authenticateDuration = _meter.CreateHistogram<double>(AuthenticateDurationName, "s", "The number of authenticate attempts. The authenticate counter is incremented by sign in methods such as PasswordSignInAsync and TwoFactorSignInAsync.");
_rememberTwoFactorClientCounter = _meter.CreateCounter<long>(RememberedTwoFactorCounterName, "{client}", "The number of two factor clients remembered.");
_forgetTwoFactorCounter = _meter.CreateCounter<long>(ForgottenTwoFactorCounterName, "{client}", "The number of two factor clients forgotten.");
_checkPasswordCounter = _meter.CreateCounter<long>(CheckPasswordAttemptsCounterName, "{attempts}", "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.");
_signInsCounter = _meter.CreateCounter<long>(SignInsCounterName, "{sign_in}", "The number of calls to sign in user principals.");
_signOutsCounter = _meter.CreateCounter<long>(SignOutsCounterName, "{sign_out}", "The number of calls to sign out user principals.");
}

internal void CheckPasswordSignIn(string userType, SignInResult? result, Exception? exception = null)
Expand All @@ -54,9 +55,9 @@ internal void CheckPasswordSignIn(string userType, SignInResult? result, Excepti
_checkPasswordCounter.Add(1, tags);
}

internal void AuthenticateSignIn(string userType, string authenticationScheme, SignInResult? result, SignInType signInType, bool? isPersistent, Exception? exception = null)
internal void AuthenticateSignIn(string userType, string authenticationScheme, SignInResult? result, SignInType signInType, bool? isPersistent, long startTimestamp, Exception? exception = null)
{
if (!_authenticateCounter.Enabled)
if (!_authenticateDuration.Enabled)
{
return;
}
Expand All @@ -71,12 +72,13 @@ internal void AuthenticateSignIn(string userType, string authenticationScheme, S
AddSignInResult(ref tags, result);
AddErrorTag(ref tags, exception);

_authenticateCounter.Add(1, tags);
var duration = ValueStopwatch.GetElapsedTime(startTimestamp, Stopwatch.GetTimestamp());
_authenticateDuration.Record(duration.TotalSeconds, tags);
}

internal void SignInUserPrincipal(string userType, string authenticationScheme, bool? isPersistent, Exception? exception = null)
{
if (!_signInUserPrincipalCounter.Enabled)
if (!_signInsCounter.Enabled)
{
return;
}
Expand All @@ -89,12 +91,12 @@ internal void SignInUserPrincipal(string userType, string authenticationScheme,
AddIsPersistent(ref tags, isPersistent);
AddErrorTag(ref tags, exception);

_signInUserPrincipalCounter.Add(1, tags);
_signInsCounter.Add(1, tags);
}

internal void SignOutUserPrincipal(string userType, string authenticationScheme, Exception? exception = null)
{
if (!_signOutUserPrincipalCounter.Enabled)
if (!_signOutsCounter.Enabled)
{
return;
}
Expand All @@ -106,7 +108,7 @@ internal void SignOutUserPrincipal(string userType, string authenticationScheme,
};
AddErrorTag(ref tags, exception);

_signOutUserPrincipalCounter.Add(1, tags);
_signOutsCounter.Add(1, tags);
}

internal void RememberTwoFactorClient(string userType, string authenticationScheme, Exception? exception = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<Reference Include="Microsoft.Extensions.Options" />

<Compile Include="$(SharedSourceRoot)TrimmingAttributes.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)ValueStopwatch\ValueStopwatch.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)ThrowHelpers\ArgumentNullThrowHelper.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)ThrowHelpers\ArgumentOutOfRangeThrowHelper.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)ThrowHelpers\ObjectDisposedThrowHelper.cs" LinkBase="Shared" />
Expand Down
Loading
Loading