Skip to content

Add metrics to Identity #62078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public interface IAuthenticationService
Task ForbidAsync(HttpContext context, string? scheme, AuthenticationProperties? properties);

/// <summary>
/// Sign a principal in for the specified authentication scheme.
/// Sign in a principal in for the specified authentication scheme.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public virtual async Task ForbidAsync(HttpContext context, string? scheme, Authe
}

/// <summary>
/// Sign a principal in for the specified authentication scheme.
/// Sign in a principal in for the specified authentication scheme.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
Expand Down
3 changes: 2 additions & 1 deletion src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="$(SharedSourceRoot)DefaultMessageEmailSender.cs" />
<Compile Include="$(SharedSourceRoot)DefaultMessageEmailSender.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)Metrics\MetricsConstants.cs" LinkBase="Shared" />
</ItemGroup>

<ItemGroup>
Expand Down
218 changes: 184 additions & 34 deletions src/Identity/Core/src/SignInManager.cs

Large diffs are not rendered by default.

192 changes: 192 additions & 0 deletions src/Identity/Core/src/SignInManagerMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// 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.Metrics;

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 RefreshCounterName = "aspnetcore.identity.sign_in.refresh";
public const string CheckPasswordCounterName = "aspnetcore.identity.sign_in.check_password";
public const string SignInUserPrincipalCounterName = "aspnetcore.identity.sign_in.sign_in_principal";
public const string SignOutUserPrincipalCounterName = "aspnetcore.identity.sign_in.sign_out_principal";

private readonly Meter _meter;
private readonly Counter<long> _authenticateCounter;
private readonly Counter<long> _rememberTwoFactorClientCounter;
private readonly Counter<long> _forgetTwoFactorCounter;
private readonly Counter<long> _refreshCounter;
private readonly Counter<long> _checkPasswordCounter;
private readonly Counter<long> _signInUserPrincipalCounter;
private readonly Counter<long> _signOutUserPrincipalCounter;

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

_authenticateCounter = _meter.CreateCounter<long>(AuthenticateCounterName, "count", "The number of authenticate and sign in attempts.");
_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.");
_refreshCounter = _meter.CreateCounter<long>(RefreshCounterName, "count", "The number of refresh sign-in attempts.");
_checkPasswordCounter = _meter.CreateCounter<long>(CheckPasswordCounterName, "count", "The number of check password attempts.");
_signInUserPrincipalCounter = _meter.CreateCounter<long>(SignInUserPrincipalCounterName, "count", "The number of user principals signed in.");
_signOutUserPrincipalCounter = _meter.CreateCounter<long>(SignOutUserPrincipalCounterName, "count", "The number of user principals signed out.");
}

internal void CheckPasswordSignIn(string userType, SignInResult? result, Exception? exception = null)
{
var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
};
AddSignInResult(ref tags, result);
AddExceptionTags(ref tags, exception);

_checkPasswordCounter.Add(1, tags);
}

internal void AuthenticateSignIn(string userType, string authenticationScheme, SignInResult? result, SignInType signInType, bool isPersistent, Exception? exception = null)
{
var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
{ "aspnetcore.identity.authentication_scheme", authenticationScheme },
{ "aspnetcore.identity.sign_in.type", GetSignInType(signInType) },
{ "aspnetcore.identity.sign_in.is_persistent", isPersistent },
};
if (result != null)
{
tags.Add("aspnetcore.identity.sign_in.result", GetSignInResult(result));
}
AddExceptionTags(ref tags, exception);

_authenticateCounter.Add(1, tags);
}

internal void SignInUserPrincipal(string userType, string authenticationScheme, Exception? exception = null)
{
var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
{ "aspnetcore.identity.authentication_scheme", authenticationScheme },
};
AddExceptionTags(ref tags, exception);

_signInUserPrincipalCounter.Add(1, tags);
}

internal void SignOutUserPrincipal(string userType, string authenticationScheme, Exception? exception = null)
{
var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
{ "aspnetcore.identity.authentication_scheme", authenticationScheme },
};
AddExceptionTags(ref tags, exception);

_signOutUserPrincipalCounter.Add(1, tags);
}

internal void RememberTwoFactorClient(string userType, string authenticationScheme, Exception? exception = null)
{
var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
{ "aspnetcore.identity.authentication_scheme", authenticationScheme }
};
AddExceptionTags(ref tags, exception);

_rememberTwoFactorClientCounter.Add(1, tags);
}

internal void ForgetTwoFactorClient(string userType, string authenticationScheme, Exception? exception = null)
{
var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
{ "aspnetcore.identity.authentication_scheme", authenticationScheme }
};
AddExceptionTags(ref tags, exception);

_forgetTwoFactorCounter.Add(1, tags);
}

internal void RefreshSignIn(string userType, string authenticationScheme, bool? success, bool? isPersistent, Exception? exception = null)
{
var tags = new TagList
{
{ "aspnetcore.identity.user_type", userType },
{ "aspnetcore.identity.authentication_scheme", authenticationScheme },
{ "aspnetcore.identity.sign_in.result", success.GetValueOrDefault() ? "success" : "failure" }
};
if (isPersistent != null)
{
tags.Add("aspnetcore.identity.sign_in.is_persistent", isPersistent.Value);
}
AddExceptionTags(ref tags, exception);

_refreshCounter.Add(1, tags);
}

public void Dispose()
{
_meter.Dispose();
}

private static void AddSignInResult(ref TagList tags, SignInResult? result)
{
if (result != null)
{
tags.Add("aspnetcore.identity.sign_in.result", GetSignInResult(result));
}
}

private static void AddExceptionTags(ref TagList tags, Exception? exception)
{
if (exception != null)
{
tags.Add("error.type", exception.GetType().FullName!);
}
}

private static string GetSignInType(SignInType signInType)
{
return signInType switch
{
SignInType.Password => "password",
SignInType.TwoFactorRecoveryCode => "two_factor_recovery_code",
SignInType.TwoFactorAuthenticator => "two_factor_authenticator",
SignInType.TwoFactor => "two_factor",
SignInType.External => "external",
_ => "_UNKNOWN"
};
}

private static string GetSignInResult(SignInResult result)
{
return result switch
{
{ Succeeded: true } => "success",
{ IsLockedOut: true } => "locked_out",
{ IsNotAllowed: true } => "not_allowed",
{ RequiresTwoFactor: true } => "requires_two_factor",
_ => "failure"
};
}
}

internal enum SignInType
{
Password,
TwoFactorRecoveryCode,
TwoFactorAuthenticator,
TwoFactor,
External
}
1 change: 1 addition & 0 deletions src/Identity/Extensions.Core/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
*REMOVED*Microsoft.AspNetCore.Identity.UserLoginInfo.UserLoginInfo(string! loginProvider, string! providerKey, string? displayName) -> void
Microsoft.AspNetCore.Identity.UserLoginInfo.UserLoginInfo(string! loginProvider, string! providerKey, string? providerDisplayName) -> void
Microsoft.AspNetCore.Identity.UserManager<TUser>.ServiceProvider.get -> System.IServiceProvider!
Loading
Loading