Skip to content

Commit e452546

Browse files
Add AuthN/AuthZ metrics (#59557)
1 parent a9daf61 commit e452546

15 files changed

+1043
-44
lines changed

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

+3-1
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.TryAddScoped<IAuthenticationService, AuthenticationService>();
23+
services.AddMetrics();
24+
services.TryAddScoped<IAuthenticationService, AuthenticationServiceImpl>();
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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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+
using System.Runtime.CompilerServices;
7+
using Microsoft.AspNetCore.Http;
8+
9+
namespace Microsoft.AspNetCore.Authentication;
10+
11+
internal sealed class AuthenticationMetrics
12+
{
13+
public const string MeterName = "Microsoft.AspNetCore.Authentication";
14+
15+
private readonly Meter _meter;
16+
private readonly Histogram<double> _authenticatedRequestDuration;
17+
private readonly Counter<long> _challengeCount;
18+
private readonly Counter<long> _forbidCount;
19+
private readonly Counter<long> _signInCount;
20+
private readonly Counter<long> _signOutCount;
21+
22+
public AuthenticationMetrics(IMeterFactory meterFactory)
23+
{
24+
_meter = meterFactory.Create(MeterName);
25+
26+
_authenticatedRequestDuration = _meter.CreateHistogram<double>(
27+
"aspnetcore.authentication.authenticate.duration",
28+
unit: "s",
29+
description: "The authentication duration for a request.",
30+
advice: new() { HistogramBucketBoundaries = MetricsConstants.ShortSecondsBucketBoundaries });
31+
32+
_challengeCount = _meter.CreateCounter<long>(
33+
"aspnetcore.authentication.challenges",
34+
unit: "{request}",
35+
description: "The total number of times a scheme is challenged.");
36+
37+
_forbidCount = _meter.CreateCounter<long>(
38+
"aspnetcore.authentication.forbids",
39+
unit: "{request}",
40+
description: "The total number of times an authenticated user attempts to access a resource they are not permitted to access.");
41+
42+
_signInCount = _meter.CreateCounter<long>(
43+
"aspnetcore.authentication.sign_ins",
44+
unit: "{request}",
45+
description: "The total number of times a principal is signed in.");
46+
47+
_signOutCount = _meter.CreateCounter<long>(
48+
"aspnetcore.authentication.sign_outs",
49+
unit: "{request}",
50+
description: "The total number of times a scheme is signed out.");
51+
}
52+
53+
public void AuthenticatedRequestCompleted(string? scheme, AuthenticateResult? result, Exception? exception, long startTimestamp, long currentTimestamp)
54+
{
55+
if (_authenticatedRequestDuration.Enabled)
56+
{
57+
AuthenticatedRequestCompletedCore(scheme, result, exception, startTimestamp, currentTimestamp);
58+
}
59+
}
60+
61+
[MethodImpl(MethodImplOptions.NoInlining)]
62+
private void AuthenticatedRequestCompletedCore(string? scheme, AuthenticateResult? result, Exception? exception, long startTimestamp, long currentTimestamp)
63+
{
64+
var tags = new TagList();
65+
66+
if (scheme is not null)
67+
{
68+
AddSchemeTag(ref tags, scheme);
69+
}
70+
71+
if (result is not null)
72+
{
73+
tags.Add("aspnetcore.authentication.result", result switch
74+
{
75+
{ None: true } => "none",
76+
{ Succeeded: true } => "success",
77+
{ Failure: not null } => "failure",
78+
_ => "_OTHER", // _OTHER is commonly used fallback for an extra or unexpected value. Shouldn't reach here.
79+
});
80+
}
81+
82+
if (exception is not null)
83+
{
84+
AddErrorTag(ref tags, exception);
85+
}
86+
87+
var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp);
88+
_authenticatedRequestDuration.Record(duration.TotalSeconds, tags);
89+
}
90+
91+
public void ChallengeCompleted(string? scheme, Exception? exception)
92+
{
93+
if (_challengeCount.Enabled)
94+
{
95+
ChallengeCompletedCore(scheme, exception);
96+
}
97+
}
98+
99+
[MethodImpl(MethodImplOptions.NoInlining)]
100+
private void ChallengeCompletedCore(string? scheme, Exception? exception)
101+
{
102+
var tags = new TagList();
103+
104+
if (scheme is not null)
105+
{
106+
AddSchemeTag(ref tags, scheme);
107+
}
108+
109+
if (exception is not null)
110+
{
111+
AddErrorTag(ref tags, exception);
112+
}
113+
114+
_challengeCount.Add(1, tags);
115+
}
116+
117+
public void ForbidCompleted(string? scheme, Exception? exception)
118+
{
119+
if (_forbidCount.Enabled)
120+
{
121+
ForbidCompletedCore(scheme, exception);
122+
}
123+
}
124+
125+
[MethodImpl(MethodImplOptions.NoInlining)]
126+
private void ForbidCompletedCore(string? scheme, Exception? exception)
127+
{
128+
var tags = new TagList();
129+
130+
if (scheme is not null)
131+
{
132+
AddSchemeTag(ref tags, scheme);
133+
}
134+
135+
if (exception is not null)
136+
{
137+
AddErrorTag(ref tags, exception);
138+
}
139+
140+
_forbidCount.Add(1, tags);
141+
}
142+
143+
public void SignInCompleted(string? scheme, Exception? exception)
144+
{
145+
if (_signInCount.Enabled)
146+
{
147+
SignInCompletedCore(scheme, exception);
148+
}
149+
}
150+
151+
[MethodImpl(MethodImplOptions.NoInlining)]
152+
private void SignInCompletedCore(string? scheme, Exception? exception)
153+
{
154+
var tags = new TagList();
155+
156+
if (scheme is not null)
157+
{
158+
AddSchemeTag(ref tags, scheme);
159+
}
160+
161+
if (exception is not null)
162+
{
163+
AddErrorTag(ref tags, exception);
164+
}
165+
166+
_signInCount.Add(1, tags);
167+
}
168+
169+
public void SignOutCompleted(string? scheme, Exception? exception)
170+
{
171+
if (_signOutCount.Enabled)
172+
{
173+
SignOutCompletedCore(scheme, exception);
174+
}
175+
}
176+
177+
[MethodImpl(MethodImplOptions.NoInlining)]
178+
private void SignOutCompletedCore(string? scheme, Exception? exception)
179+
{
180+
var tags = new TagList();
181+
182+
if (scheme is not null)
183+
{
184+
AddSchemeTag(ref tags, scheme);
185+
}
186+
187+
if (exception is not null)
188+
{
189+
AddErrorTag(ref tags, exception);
190+
}
191+
192+
_signOutCount.Add(1, tags);
193+
}
194+
195+
private static void AddSchemeTag(ref TagList tags, string scheme)
196+
{
197+
tags.Add("aspnetcore.authentication.scheme", scheme);
198+
}
199+
200+
private static void AddErrorTag(ref TagList tags, Exception exception)
201+
{
202+
tags.Add("error.type", exception.GetType().FullName);
203+
}
204+
}

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

+14-35
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ public class AuthenticationService : IAuthenticationService
2222
/// <param name="handlers">The <see cref="IAuthenticationHandlerProvider"/>.</param>
2323
/// <param name="transform">The <see cref="IClaimsTransformation"/>.</param>
2424
/// <param name="options">The <see cref="AuthenticationOptions"/>.</param>
25-
public AuthenticationService(IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform, IOptions<AuthenticationOptions> options)
25+
public AuthenticationService(
26+
IAuthenticationSchemeProvider schemes,
27+
IAuthenticationHandlerProvider handlers,
28+
IClaimsTransformation transform,
29+
IOptions<AuthenticationOptions> options)
2630
{
2731
Schemes = schemes;
2832
Handlers = handlers;
@@ -68,11 +72,7 @@ public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext cont
6872
}
6973
}
7074

71-
var handler = await Handlers.GetHandlerAsync(context, scheme);
72-
if (handler == null)
73-
{
74-
throw await CreateMissingHandlerException(scheme);
75-
}
75+
var handler = await Handlers.GetHandlerAsync(context, scheme) ?? throw await CreateMissingHandlerException(scheme);
7676

7777
// Handlers should not return null, but we'll be tolerant of null values for legacy reasons.
7878
var result = (await handler.AuthenticateAsync()) ?? AuthenticateResult.NoResult();
@@ -81,7 +81,7 @@ public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext cont
8181
{
8282
var principal = result.Principal!;
8383
var doTransform = true;
84-
_transformCache ??= new HashSet<ClaimsPrincipal>();
84+
_transformCache ??= [];
8585
if (_transformCache.Contains(principal))
8686
{
8787
doTransform = false;
@@ -94,6 +94,7 @@ public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext cont
9494
}
9595
return AuthenticateResult.Success(new AuthenticationTicket(principal, result.Properties, result.Ticket!.AuthenticationScheme));
9696
}
97+
9798
return result;
9899
}
99100

@@ -116,12 +117,7 @@ public virtual async Task ChallengeAsync(HttpContext context, string? scheme, Au
116117
}
117118
}
118119

119-
var handler = await Handlers.GetHandlerAsync(context, scheme);
120-
if (handler == null)
121-
{
122-
throw await CreateMissingHandlerException(scheme);
123-
}
124-
120+
var handler = await Handlers.GetHandlerAsync(context, scheme) ?? throw await CreateMissingHandlerException(scheme);
125121
await handler.ChallengeAsync(properties);
126122
}
127123

@@ -144,12 +140,7 @@ public virtual async Task ForbidAsync(HttpContext context, string? scheme, Authe
144140
}
145141
}
146142

147-
var handler = await Handlers.GetHandlerAsync(context, scheme);
148-
if (handler == null)
149-
{
150-
throw await CreateMissingHandlerException(scheme);
151-
}
152-
143+
var handler = await Handlers.GetHandlerAsync(context, scheme) ?? throw await CreateMissingHandlerException(scheme);
153144
await handler.ForbidAsync(properties);
154145
}
155146

@@ -187,14 +178,8 @@ public virtual async Task SignInAsync(HttpContext context, string? scheme, Claim
187178
}
188179
}
189180

190-
var handler = await Handlers.GetHandlerAsync(context, scheme);
191-
if (handler == null)
192-
{
193-
throw await CreateMissingSignInHandlerException(scheme);
194-
}
195-
196-
var signInHandler = handler as IAuthenticationSignInHandler;
197-
if (signInHandler == null)
181+
var handler = await Handlers.GetHandlerAsync(context, scheme) ?? throw await CreateMissingSignInHandlerException(scheme);
182+
if (handler is not IAuthenticationSignInHandler signInHandler)
198183
{
199184
throw await CreateMismatchedSignInHandlerException(scheme, handler);
200185
}
@@ -221,14 +206,8 @@ public virtual async Task SignOutAsync(HttpContext context, string? scheme, Auth
221206
}
222207
}
223208

224-
var handler = await Handlers.GetHandlerAsync(context, scheme);
225-
if (handler == null)
226-
{
227-
throw await CreateMissingSignOutHandlerException(scheme);
228-
}
229-
230-
var signOutHandler = handler as IAuthenticationSignOutHandler;
231-
if (signOutHandler == null)
209+
var handler = await Handlers.GetHandlerAsync(context, scheme) ?? throw await CreateMissingSignOutHandlerException(scheme);
210+
if (handler is not IAuthenticationSignOutHandler signOutHandler)
232211
{
233212
throw await CreateMismatchedSignOutHandlerException(scheme, handler);
234213
}

0 commit comments

Comments
 (0)