Skip to content

Commit e50ed9c

Browse files
committed
Add authentication metrics
1 parent faf472d commit e50ed9c

File tree

7 files changed

+369
-2
lines changed

7 files changed

+369
-2
lines changed

src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ public static IServiceCollection AddAuthenticationCore(this IServiceCollection s
2020
{
2121
ArgumentNullException.ThrowIfNull(services);
2222

23+
services.AddMetrics();
2324
services.TryAddScoped<IAuthenticationService, AuthenticationService>();
2425
services.TryAddSingleton<IClaimsTransformation, NoopClaimsTransformation>(); // Can be replaced with scoped ones that use DbContext
2526
services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();
2627
services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();
28+
services.TryAddSingleton<AuthenticationMetrics>();
2729
return services;
2830
}
2931

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Diagnostics.Metrics;
6+
7+
namespace Microsoft.AspNetCore.Authentication;
8+
9+
internal sealed class AuthenticationMetrics : IDisposable
10+
{
11+
public const string MeterName = "Microsoft.AspNetCore.Authentication";
12+
13+
private readonly Meter _meter;
14+
private readonly Counter<long> _authenticatedRequestCount;
15+
private readonly Counter<long> _challengeCount;
16+
private readonly Counter<long> _forbidCount;
17+
private readonly Counter<long> _signInCount;
18+
private readonly Counter<long> _signOutCount;
19+
20+
public AuthenticationMetrics(IMeterFactory meterFactory)
21+
{
22+
_meter = meterFactory.Create(MeterName);
23+
24+
_authenticatedRequestCount = _meter.CreateCounter<long>(
25+
"aspnetcore.authentication.authenticated_requests",
26+
unit: "{request}",
27+
description: "The total number of authenticated requests");
28+
29+
_challengeCount = _meter.CreateCounter<long>(
30+
"aspnetcore.authentication.challenges",
31+
unit: "{request}",
32+
description: "The total number of times a scheme is challenged");
33+
34+
_forbidCount = _meter.CreateCounter<long>(
35+
"aspnetcore.authentication.forbids",
36+
unit: "{request}",
37+
description: "The total number of times an authenticated user attempts to access a resource they are not permitted to access");
38+
39+
_signInCount = _meter.CreateCounter<long>(
40+
"aspnetcore.authentication.sign_ins",
41+
unit: "{request}",
42+
description: "The total number of times a principal is signed in");
43+
44+
_signOutCount = _meter.CreateCounter<long>(
45+
"aspnetcore.authentication.sign_outs",
46+
unit: "{request}",
47+
description: "The total number of times a scheme is signed out");
48+
}
49+
50+
public void AuthenticatedRequest(string scheme, AuthenticateResult result)
51+
{
52+
if (_authenticatedRequestCount.Enabled)
53+
{
54+
var resultTagValue = result switch
55+
{
56+
{ Succeeded: true } => "success",
57+
{ Failure: not null } => "failure",
58+
{ None: true } => "none",
59+
_ => throw new UnreachableException($"Could not determine the result state of the {nameof(AuthenticateResult)}"),
60+
};
61+
62+
_authenticatedRequestCount.Add(1, [
63+
new("aspnetcore.authentication.scheme", scheme),
64+
new("aspnetcore.authentication.result", resultTagValue),
65+
]);
66+
}
67+
}
68+
69+
public void Challenge(string scheme)
70+
{
71+
if (_challengeCount.Enabled)
72+
{
73+
_challengeCount.Add(1, [new("aspnetcore.authentication.scheme", scheme)]);
74+
}
75+
}
76+
77+
public void Forbid(string scheme)
78+
{
79+
if (_forbidCount.Enabled)
80+
{
81+
_forbidCount.Add(1, [new("aspnetcore.authentication.scheme", scheme)]);
82+
}
83+
}
84+
85+
public void SignIn(string scheme)
86+
{
87+
if (_signInCount.Enabled)
88+
{
89+
_signInCount.Add(1, [new("aspnetcore.authentication.scheme", scheme)]);
90+
}
91+
}
92+
93+
public void SignOut(string scheme)
94+
{
95+
if (_signOutCount.Enabled)
96+
{
97+
_signOutCount.Add(1, [new("aspnetcore.authentication.scheme", scheme)]);
98+
}
99+
}
100+
101+
public void Dispose()
102+
{
103+
_meter.Dispose();
104+
}
105+
}

src/Http/Authentication.Core/src/AuthenticationService.cs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using System.Security.Claims;
66
using Microsoft.AspNetCore.Http;
7+
using Microsoft.Extensions.DependencyInjection;
78
using Microsoft.Extensions.Options;
89

910
namespace Microsoft.AspNetCore.Authentication;
@@ -13,6 +14,8 @@ namespace Microsoft.AspNetCore.Authentication;
1314
/// </summary>
1415
public class AuthenticationService : IAuthenticationService
1516
{
17+
private readonly AuthenticationMetrics? _metrics;
18+
1619
private HashSet<ClaimsPrincipal>? _transformCache;
1720

1821
/// <summary>
@@ -22,12 +25,36 @@ public class AuthenticationService : IAuthenticationService
2225
/// <param name="handlers">The <see cref="IAuthenticationHandlerProvider"/>.</param>
2326
/// <param name="transform">The <see cref="IClaimsTransformation"/>.</param>
2427
/// <param name="options">The <see cref="AuthenticationOptions"/>.</param>
25-
public AuthenticationService(IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform, IOptions<AuthenticationOptions> options)
28+
public AuthenticationService(
29+
IAuthenticationSchemeProvider schemes,
30+
IAuthenticationHandlerProvider handlers,
31+
IClaimsTransformation transform,
32+
IOptions<AuthenticationOptions> options)
33+
: this(schemes, handlers, transform, options, services: null)
34+
{
35+
}
36+
37+
/// <summary>
38+
/// Constructor.
39+
/// </summary>
40+
/// <param name="schemes">The <see cref="IAuthenticationSchemeProvider"/>.</param>
41+
/// <param name="handlers">The <see cref="IAuthenticationHandlerProvider"/>.</param>
42+
/// <param name="transform">The <see cref="IClaimsTransformation"/>.</param>
43+
/// <param name="options">The <see cref="AuthenticationOptions"/>.</param>
44+
/// <param name="services">The <see cref="IServiceProvider"/>.</param>
45+
public AuthenticationService(
46+
IAuthenticationSchemeProvider schemes,
47+
IAuthenticationHandlerProvider handlers,
48+
IClaimsTransformation transform,
49+
IOptions<AuthenticationOptions> options,
50+
IServiceProvider? services)
2651
{
2752
Schemes = schemes;
2853
Handlers = handlers;
2954
Transform = transform;
3055
Options = options.Value;
56+
57+
_metrics = services?.GetService<AuthenticationMetrics>();
3158
}
3259

3360
/// <summary>
@@ -77,11 +104,13 @@ public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext cont
77104
// Handlers should not return null, but we'll be tolerant of null values for legacy reasons.
78105
var result = (await handler.AuthenticateAsync()) ?? AuthenticateResult.NoResult();
79106

107+
_metrics?.AuthenticatedRequest(scheme, result);
108+
80109
if (result.Succeeded)
81110
{
82111
var principal = result.Principal!;
83112
var doTransform = true;
84-
_transformCache ??= new HashSet<ClaimsPrincipal>();
113+
_transformCache ??= [];
85114
if (_transformCache.Contains(principal))
86115
{
87116
doTransform = false;
@@ -122,6 +151,8 @@ public virtual async Task ChallengeAsync(HttpContext context, string? scheme, Au
122151
throw await CreateMissingHandlerException(scheme);
123152
}
124153

154+
_metrics?.Challenge(scheme);
155+
125156
await handler.ChallengeAsync(properties);
126157
}
127158

@@ -150,6 +181,8 @@ public virtual async Task ForbidAsync(HttpContext context, string? scheme, Authe
150181
throw await CreateMissingHandlerException(scheme);
151182
}
152183

184+
_metrics?.Forbid(scheme);
185+
153186
await handler.ForbidAsync(properties);
154187
}
155188

@@ -199,6 +232,8 @@ public virtual async Task SignInAsync(HttpContext context, string? scheme, Claim
199232
throw await CreateMismatchedSignInHandlerException(scheme, handler);
200233
}
201234

235+
_metrics?.SignIn(scheme);
236+
202237
await signInHandler.SignInAsync(principal, properties);
203238
}
204239

@@ -233,6 +268,8 @@ public virtual async Task SignOutAsync(HttpContext context, string? scheme, Auth
233268
throw await CreateMismatchedSignOutHandlerException(scheme, handler);
234269
}
235270

271+
_metrics?.SignOut(scheme);
272+
236273
await signOutHandler.SignOutAsync(properties);
237274
}
238275

src/Http/Authentication.Core/src/Microsoft.AspNetCore.Authentication.Core.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,8 @@
1616
<Reference Include="Microsoft.AspNetCore.Http.Extensions" />
1717
</ItemGroup>
1818

19+
<ItemGroup>
20+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Authentication.Test" />
21+
</ItemGroup>
22+
1923
</Project>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticationService(Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider! schemes, Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider! handlers, Microsoft.AspNetCore.Authentication.IClaimsTransformation! transform, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Authentication.AuthenticationOptions!>! options, System.IServiceProvider? services) -> void

0 commit comments

Comments
 (0)