Skip to content

Commit 30727e0

Browse files
committed
Add metrics to Identity
1 parent c5d6996 commit 30727e0

File tree

12 files changed

+1574
-238
lines changed

12 files changed

+1574
-238
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
</PropertyGroup>
1414

1515
<ItemGroup>
16-
<Compile Include="$(SharedSourceRoot)DefaultMessageEmailSender.cs" />
16+
<Compile Include="$(SharedSourceRoot)DefaultMessageEmailSender.cs" LinkBase="Shared" />
17+
<Compile Include="$(SharedSourceRoot)Metrics\MetricsConstants.cs" LinkBase="Shared" />
1718
</ItemGroup>
1819

1920
<ItemGroup>

src/Identity/Core/src/SignInManager.cs

Lines changed: 149 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Text;
88
using Microsoft.AspNetCore.Authentication;
99
using Microsoft.AspNetCore.Http;
10+
using Microsoft.Extensions.DependencyInjection;
1011
using Microsoft.Extensions.Logging;
1112
using Microsoft.Extensions.Options;
1213

@@ -24,6 +25,7 @@ public class SignInManager<TUser> where TUser : class
2425
private readonly IHttpContextAccessor _contextAccessor;
2526
private readonly IAuthenticationSchemeProvider _schemes;
2627
private readonly IUserConfirmation<TUser> _confirmation;
28+
private readonly SignInManagerMetrics? _metrics;
2729
private HttpContext? _context;
2830
private TwoFactorAuthenticationInfo? _twoFactorInfo;
2931

@@ -56,6 +58,7 @@ public SignInManager(UserManager<TUser> userManager,
5658
Logger = logger;
5759
_schemes = schemes;
5860
_confirmation = confirmation;
61+
_metrics = userManager.ServiceProvider?.GetService<SignInManagerMetrics>();
5962
}
6063

6164
/// <summary>
@@ -160,25 +163,39 @@ public virtual async Task<bool> CanSignInAsync(TUser user)
160163
/// <param name="user">The user to sign-in.</param>
161164
/// <returns>The task object representing the asynchronous operation.</returns>
162165
public virtual async Task RefreshSignInAsync(TUser user)
166+
{
167+
try
168+
{
169+
var (success, isPersistent) = await RefreshSignInCoreAsync(user);
170+
_metrics?.RefreshSignIn(typeof(TUser).FullName!, AuthenticationScheme, success, isPersistent);
171+
}
172+
catch (Exception ex)
173+
{
174+
_metrics?.RefreshSignIn(typeof(TUser).FullName!, AuthenticationScheme, success: null, isPersistent: null, ex);
175+
throw;
176+
}
177+
}
178+
179+
private async Task<(bool success, bool? isPersistent)> RefreshSignInCoreAsync(TUser user)
163180
{
164181
var auth = await Context.AuthenticateAsync(AuthenticationScheme);
165182
if (!auth.Succeeded || auth.Principal?.Identity?.IsAuthenticated != true)
166183
{
167184
Logger.LogError("RefreshSignInAsync prevented because the user is not currently authenticated. Use SignInAsync instead for initial sign in.");
168-
return;
185+
return (false, auth.Properties?.IsPersistent);
169186
}
170187

171188
var authenticatedUserId = UserManager.GetUserId(auth.Principal);
172189
var newUserId = await UserManager.GetUserIdAsync(user);
173190
if (authenticatedUserId == null || authenticatedUserId != newUserId)
174191
{
175192
Logger.LogError("RefreshSignInAsync prevented because currently authenticated user has a different UserId. Use SignInAsync instead to change users.");
176-
return;
193+
return (false, auth.Properties?.IsPersistent);
177194
}
178195

179196
IList<Claim> claims = Array.Empty<Claim>();
180-
var authenticationMethod = auth?.Principal?.FindFirst(ClaimTypes.AuthenticationMethod);
181-
var amr = auth?.Principal?.FindFirst("amr");
197+
var authenticationMethod = auth.Principal?.FindFirst(ClaimTypes.AuthenticationMethod);
198+
var amr = auth.Principal?.FindFirst("amr");
182199

183200
if (authenticationMethod != null || amr != null)
184201
{
@@ -193,7 +210,8 @@ public virtual async Task RefreshSignInAsync(TUser user)
193210
}
194211
}
195212

196-
await SignInWithClaimsAsync(user, auth?.Properties, claims);
213+
await SignInWithClaimsAsync(user, auth.Properties, claims);
214+
return (true, auth.Properties?.IsPersistent ?? false);
197215
}
198216

199217
/// <summary>
@@ -345,12 +363,23 @@ public virtual async Task<bool> ValidateSecurityStampAsync(TUser? user, string?
345363
public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string password,
346364
bool isPersistent, bool lockoutOnFailure)
347365
{
348-
ArgumentNullException.ThrowIfNull(user);
366+
try
367+
{
368+
ArgumentNullException.ThrowIfNull(user);
369+
370+
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
371+
var result = attempt.Succeeded
372+
? await SignInOrTwoFactorAsync(user, isPersistent)
373+
: attempt;
374+
_metrics?.SignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Password, isPersistent);
349375

350-
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
351-
return attempt.Succeeded
352-
? await SignInOrTwoFactorAsync(user, isPersistent)
353-
: attempt;
376+
return result;
377+
}
378+
catch (Exception ex)
379+
{
380+
_metrics?.SignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Password, isPersistent, ex);
381+
throw;
382+
}
354383
}
355384

356385
/// <summary>
@@ -369,6 +398,7 @@ public virtual async Task<SignInResult> PasswordSignInAsync(string userName, str
369398
var user = await UserManager.FindByNameAsync(userName);
370399
if (user == null)
371400
{
401+
_metrics?.SignIn(typeof(TUser).FullName!, AuthenticationScheme, SignInResult.Failed, SignInType.Password, isPersistent);
372402
return SignInResult.Failed;
373403
}
374404

@@ -386,8 +416,24 @@ public virtual async Task<SignInResult> PasswordSignInAsync(string userName, str
386416
/// <returns></returns>
387417
public virtual async Task<SignInResult> CheckPasswordSignInAsync(TUser user, string password, bool lockoutOnFailure)
388418
{
389-
ArgumentNullException.ThrowIfNull(user);
419+
try
420+
{
421+
ArgumentNullException.ThrowIfNull(user);
422+
423+
var result = await CheckPasswordSignInCoreAsync(user, password, lockoutOnFailure);
424+
_metrics?.CheckPasswordSignIn(typeof(TUser).FullName!, result);
425+
426+
return result;
427+
}
428+
catch (Exception ex)
429+
{
430+
_metrics?.CheckPasswordSignIn(typeof(TUser).FullName!, result: null, ex);
431+
throw;
432+
}
433+
}
390434

435+
private async Task<SignInResult> CheckPasswordSignInCoreAsync(TUser user, string password, bool lockoutOnFailure)
436+
{
391437
var error = await PreSignInCheck(user);
392438
if (error != null)
393439
{
@@ -461,19 +507,37 @@ public virtual async Task<bool> IsTwoFactorClientRememberedAsync(TUser user)
461507
/// <returns>The task object representing the asynchronous operation.</returns>
462508
public virtual async Task RememberTwoFactorClientAsync(TUser user)
463509
{
464-
var principal = await StoreRememberClient(user);
465-
await Context.SignInAsync(IdentityConstants.TwoFactorRememberMeScheme,
466-
principal,
467-
new AuthenticationProperties { IsPersistent = true });
510+
try
511+
{
512+
var principal = await StoreRememberClient(user);
513+
await Context.SignInAsync(IdentityConstants.TwoFactorRememberMeScheme,
514+
principal,
515+
new AuthenticationProperties { IsPersistent = true });
516+
_metrics?.RememberTwoFactorClient(typeof(TUser).FullName!, IdentityConstants.TwoFactorRememberMeScheme);
517+
}
518+
catch (Exception ex)
519+
{
520+
_metrics?.RememberTwoFactorClient(typeof(TUser).FullName!, IdentityConstants.TwoFactorRememberMeScheme, ex);
521+
throw;
522+
}
468523
}
469524

470525
/// <summary>
471526
/// Clears the "Remember this browser flag" from the current browser, as an asynchronous operation.
472527
/// </summary>
473528
/// <returns>The task object representing the asynchronous operation.</returns>
474-
public virtual Task ForgetTwoFactorClientAsync()
529+
public virtual async Task ForgetTwoFactorClientAsync()
475530
{
476-
return Context.SignOutAsync(IdentityConstants.TwoFactorRememberMeScheme);
531+
try
532+
{
533+
await Context.SignOutAsync(IdentityConstants.TwoFactorRememberMeScheme);
534+
_metrics?.ForgetTwoFactorClient(typeof(TUser).FullName!, IdentityConstants.TwoFactorRememberMeScheme);
535+
}
536+
catch (Exception ex)
537+
{
538+
_metrics?.ForgetTwoFactorClient(typeof(TUser).FullName!, IdentityConstants.TwoFactorRememberMeScheme, ex);
539+
throw;
540+
}
477541
}
478542

479543
/// <summary>
@@ -482,6 +546,22 @@ public virtual Task ForgetTwoFactorClientAsync()
482546
/// <param name="recoveryCode">The two factor recovery code.</param>
483547
/// <returns></returns>
484548
public virtual async Task<SignInResult> TwoFactorRecoveryCodeSignInAsync(string recoveryCode)
549+
{
550+
try
551+
{
552+
var result = await TwoFactorRecoveryCodeSignInCoreAsync(recoveryCode);
553+
_metrics?.SignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorRecoveryCode, isPersistent: false);
554+
555+
return result;
556+
}
557+
catch (Exception ex)
558+
{
559+
_metrics?.SignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorRecoveryCode, isPersistent: false, ex);
560+
throw;
561+
}
562+
}
563+
564+
private async Task<SignInResult> TwoFactorRecoveryCodeSignInCoreAsync(string recoveryCode)
485565
{
486566
var twoFactorInfo = await RetrieveTwoFactorInfoAsync();
487567
if (twoFactorInfo == null)
@@ -510,8 +590,10 @@ private async Task<SignInResult> DoTwoFactorSignInAsync(TUser user, TwoFactorAut
510590
return SignInResult.Failed;
511591
}
512592

513-
var claims = new List<Claim>();
514-
claims.Add(new Claim("amr", "mfa"));
593+
var claims = new List<Claim>
594+
{
595+
new Claim("amr", "mfa")
596+
};
515597

516598
if (twoFactorInfo.LoginProvider != null)
517599
{
@@ -545,6 +627,22 @@ private async Task<SignInResult> DoTwoFactorSignInAsync(TUser user, TwoFactorAut
545627
/// <returns>The task object representing the asynchronous operation containing the <see name="SignInResult"/>
546628
/// for the sign-in attempt.</returns>
547629
public virtual async Task<SignInResult> TwoFactorAuthenticatorSignInAsync(string code, bool isPersistent, bool rememberClient)
630+
{
631+
try
632+
{
633+
var result = await TwoFactorAuthenticatorSignInCoreAsync(code, isPersistent, rememberClient);
634+
_metrics?.SignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorAuthenticator, isPersistent);
635+
636+
return result;
637+
}
638+
catch (Exception ex)
639+
{
640+
_metrics?.SignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorAuthenticator, isPersistent, ex);
641+
throw;
642+
}
643+
}
644+
645+
private async Task<SignInResult> TwoFactorAuthenticatorSignInCoreAsync(string code, bool isPersistent, bool rememberClient)
548646
{
549647
var twoFactorInfo = await RetrieveTwoFactorInfoAsync();
550648
if (twoFactorInfo == null)
@@ -593,6 +691,22 @@ public virtual async Task<SignInResult> TwoFactorAuthenticatorSignInAsync(string
593691
/// <returns>The task object representing the asynchronous operation containing the <see name="SignInResult"/>
594692
/// for the sign-in attempt.</returns>
595693
public virtual async Task<SignInResult> TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient)
694+
{
695+
try
696+
{
697+
var result = await TwoFactorSignInCoreAsync(provider, code, isPersistent, rememberClient);
698+
_metrics?.SignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactor, isPersistent);
699+
700+
return result;
701+
}
702+
catch (Exception ex)
703+
{
704+
_metrics?.SignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactor, isPersistent, ex);
705+
throw;
706+
}
707+
}
708+
709+
private async Task<SignInResult> TwoFactorSignInCoreAsync(string provider, string code, bool isPersistent, bool rememberClient)
596710
{
597711
var twoFactorInfo = await RetrieveTwoFactorInfoAsync();
598712
if (twoFactorInfo == null)
@@ -666,6 +780,22 @@ public virtual Task<SignInResult> ExternalLoginSignInAsync(string loginProvider,
666780
/// <returns>The task object representing the asynchronous operation containing the <see name="SignInResult"/>
667781
/// for the sign-in attempt.</returns>
668782
public virtual async Task<SignInResult> ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, bool bypassTwoFactor)
783+
{
784+
try
785+
{
786+
var result = await ExternalLoginSignInCoreAsync(loginProvider, providerKey, isPersistent, bypassTwoFactor);
787+
_metrics?.SignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.External, isPersistent);
788+
789+
return result;
790+
}
791+
catch (Exception ex)
792+
{
793+
_metrics?.SignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.External, isPersistent, ex);
794+
throw;
795+
}
796+
}
797+
798+
private async Task<SignInResult> ExternalLoginSignInCoreAsync(string loginProvider, string providerKey, bool isPersistent, bool bypassTwoFactor)
669799
{
670800
var user = await UserManager.FindByLoginAsync(loginProvider, providerKey);
671801
if (user == null)

0 commit comments

Comments
 (0)