Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
83 changes: 55 additions & 28 deletions src/Identity/Core/src/SignInManagerMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,64 @@

using System.Diagnostics;
using System.Diagnostics.Metrics;
using Microsoft.AspNetCore.Http;
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.two_factor_clients_remembered";
public const string ForgottenTwoFactorCounterName = "aspnetcore.identity.sign_in.two_factor_clients_forgotten";
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,
unit: "s",
description: "The duration of authenticate attempts. The authenticate metrics is recorded by sign in methods such as PasswordSignInAsync and TwoFactorSignInAsync.",
advice: new() { HistogramBucketBoundaries = MetricsConstants.ShortSecondsBucketBoundaries });

_rememberTwoFactorClientCounter = _meter.CreateCounter<long>(
RememberedTwoFactorCounterName,
unit: "{client}",
description: "The total number of two factor clients remembered.");

_forgetTwoFactorCounter = _meter.CreateCounter<long>(
ForgottenTwoFactorCounterName,
unit: "{client}",
description: "The total number of two factor clients forgotten.");

_checkPasswordCounter = _meter.CreateCounter<long>(
CheckPasswordAttemptsCounterName,
unit: "{attempt}",
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.");

_signInsCounter = _meter.CreateCounter<long>(
SignInsCounterName,
unit: "{sign_in}",
description: "The total number of calls to sign in user principals.");

_signOutsCounter = _meter.CreateCounter<long>(
SignOutsCounterName,
unit: "{sign_out}",
description: "The total number of calls to sign out user principals.");
}

internal void CheckPasswordSignIn(string userType, SignInResult? result, Exception? exception = null)
Expand All @@ -54,59 +80,60 @@ 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;
}

var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
{ "aspnetcore.identity.authentication_scheme", authenticationScheme },
{ "aspnetcore.authentication.scheme", authenticationScheme },
{ "aspnetcore.identity.sign_in.type", GetSignInType(signInType) },
};
AddIsPersistent(ref tags, isPersistent);
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;
}

var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
{ "aspnetcore.identity.authentication_scheme", authenticationScheme },
{ "aspnetcore.authentication.scheme", 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;
}

var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
{ "aspnetcore.identity.authentication_scheme", authenticationScheme },
{ "aspnetcore.authentication.scheme", authenticationScheme },
};
AddErrorTag(ref tags, exception);

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

internal void RememberTwoFactorClient(string userType, string authenticationScheme, Exception? exception = null)
Expand All @@ -119,7 +146,7 @@ internal void RememberTwoFactorClient(string userType, string authenticationSche
var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
{ "aspnetcore.identity.authentication_scheme", authenticationScheme }
{ "aspnetcore.authentication.scheme", authenticationScheme }
};
AddErrorTag(ref tags, exception);

Expand All @@ -136,7 +163,7 @@ internal void ForgetTwoFactorClient(string userType, string authenticationScheme
var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
{ "aspnetcore.identity.authentication_scheme", authenticationScheme }
{ "aspnetcore.authentication.scheme", authenticationScheme }
};
AddErrorTag(ref tags, exception);

Expand All @@ -152,7 +179,7 @@ private static void AddIsPersistent(ref TagList tags, bool? isPersistent)
{
if (isPersistent != null)
{
tags.Add("aspnetcore.identity.sign_in.is_persistent", isPersistent.Value);
tags.Add("aspnetcore.authentication.is_persistent", isPersistent.Value);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
<Reference Include="Microsoft.Extensions.Logging" />
<Reference Include="Microsoft.Extensions.Options" />

<Compile Include="$(SharedSourceRoot)Metrics\MetricsConstants.cs" LinkBase="Shared" />
<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