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
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ public static IIdentityServerBuilder AddCoreServices(this IIdentityServerBuilder
builder.Services.AddSingleton<IDiagnosticEntry, RegisteredImplementationsDiagnosticEntry>();
builder.Services.AddSingleton<IDiagnosticEntry, IdentityServerOptionsDiagnosticEntry>();
builder.Services.AddSingleton<IDiagnosticEntry, DataProtectionDiagnosticEntry>();
builder.Services.AddSingleton<IDiagnosticEntry, TokenIssueCountDiagnosticEntry>();
builder.Services.AddSingleton<DiagnosticSummary>();
builder.Services.AddHostedService<DiagnosticHostedService>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,12 @@ private Task RaiseResponseEventAsync(AuthorizeResponse response)
Telemetry.Metrics.TokenIssued(
response.Request.ClientId,
response.Request.GrantType,
response.Request.AuthorizeRequestType);
response.Request.AuthorizeRequestType,
response.AccessToken.IsPresent(),
response.AccessToken.IsPresent() ? response.Request.AccessTokenType : null,
false,
ProofType.None,
response.IdentityToken.IsPresent());
return _events.RaiseAsync(new TokenIssuedSuccessEvent(response));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ private async Task<IEndpointResult> ProcessTokenRequestAsync(HttpContext context
var response = await _responseGenerator.ProcessAsync(requestResult);

await _events.RaiseAsync(new TokenIssuedSuccessEvent(response, requestResult));
Telemetry.Metrics.TokenIssued(clientResult.Client.ClientId, requestResult.ValidatedRequest.GrantType, null);

Telemetry.Metrics.TokenIssued(clientResult.Client.ClientId, requestResult.ValidatedRequest.GrantType, null,
response.AccessToken.IsPresent(), response.AccessTokenType.IsPresent() ? requestResult.ValidatedRequest.AccessTokenType : null, response.RefreshToken.IsPresent(),
requestResult.ValidatedRequest.ProofType, response.IdentityToken.IsPresent());
LogTokens(response, requestResult);

// return result
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

#nullable enable
using System.Diagnostics.Metrics;
using System.Text.Json;
using Duende.IdentityServer.Models;

namespace Duende.IdentityServer.Licensing.V2.Diagnostics.DiagnosticEntries;

internal class TokenIssueCountDiagnosticEntry : IDiagnosticEntry
{
private long _jwtTokenIssued;
private long _referenceTokenIssued;
private long _refreshTokenIssued;
private long _jwtPoPDPoPTokenIssued;
private long _referencePoPDPoPTokenIssued;
private long _jwtPoPmTLSTokenIssued;
private long _referencePoPmTLSTokenIssued;
private long _idTokenIssued;

private long _implicitGrantTypeFlows;
private long _hybridGrantTypeFlows;
private long _authorizationCodeGrantTypeFlows;
private long _clientCredentialsGrantTypeFlows;
private long _resourceOwnerPasswordGrantTypeFlows;
private long _deviceFlowGrantTypeFlows;
private long _otherGrantTypeFlows;

private readonly MeterListener _meterListener;

public TokenIssueCountDiagnosticEntry()
{
_meterListener = new MeterListener();

_meterListener.InstrumentPublished += (instrument, listener) =>
{
if (instrument.Name == Telemetry.Metrics.Counters.TokenIssued)
{
listener.EnableMeasurementEvents(instrument);
}
};

_meterListener.SetMeasurementEventCallback<long>(HandleLongMeasurementRecorded);

_meterListener.Start();
}

public Task WriteAsync(Utf8JsonWriter writer)
{
writer.WritePropertyName("TokenIssueCounts");
writer.WriteStartObject();

writer.WriteNumber("Jwt", _jwtTokenIssued);
writer.WriteNumber("Reference", _referenceTokenIssued);
writer.WriteNumber("JwtPoPDPoP", _jwtPoPDPoPTokenIssued);
writer.WriteNumber("ReferencePoPDPoP", _referencePoPDPoPTokenIssued);
writer.WriteNumber("JwtPoPmTLS", _jwtPoPmTLSTokenIssued);
writer.WriteNumber("ReferencePoPmTLS", _referencePoPmTLSTokenIssued);
writer.WriteNumber("Refresh", _refreshTokenIssued);
writer.WriteNumber("Id", _idTokenIssued);
writer.WriteNumber(GrantType.Implicit, _implicitGrantTypeFlows);
writer.WriteNumber(GrantType.Hybrid, _hybridGrantTypeFlows);
writer.WriteNumber(GrantType.AuthorizationCode, _authorizationCodeGrantTypeFlows);
writer.WriteNumber(GrantType.ClientCredentials, _clientCredentialsGrantTypeFlows);
writer.WriteNumber(GrantType.ResourceOwnerPassword, _resourceOwnerPasswordGrantTypeFlows);
writer.WriteNumber(GrantType.DeviceFlow, _deviceFlowGrantTypeFlows);
writer.WriteNumber("Other", _otherGrantTypeFlows);

writer.WriteEndObject();

return Task.CompletedTask;
}

private void HandleLongMeasurementRecorded(Instrument instrument, long value, ReadOnlySpan<KeyValuePair<string, object?>> tags, object? state)
{
if (instrument.Name != Telemetry.Metrics.Counters.TokenIssued)
{
return;
}

var accessTokenIssued = false;
var accessTokenType = AccessTokenType.Jwt;
var refreshTokenIssued = false;
var proofType = ProofType.None;
var identityTokenIssued = false;
var grantType = string.Empty;

foreach (var tag in tags)
{
switch (tag.Key)
{
case Telemetry.Metrics.Tags.AccessTokenType:
if (!Enum.TryParse(tag.Value?.ToString(), out accessTokenType))
{
accessTokenType = AccessTokenType.Jwt;
}
break;
case Telemetry.Metrics.Tags.RefreshTokenIssued:
bool.TryParse(tag.Value?.ToString(), out refreshTokenIssued);
break;
case Telemetry.Metrics.Tags.ProofType:
if (!Enum.TryParse(tag.Value?.ToString(), out proofType))
{
proofType = ProofType.None;
}
break;
case Telemetry.Metrics.Tags.AccessTokenIssued:
bool.TryParse(tag.Value?.ToString(), out accessTokenIssued);
break;
case Telemetry.Metrics.Tags.IdTokenIssued:
bool.TryParse(tag.Value?.ToString(), out identityTokenIssued);
break;
case Telemetry.Metrics.Tags.GrantType:
grantType = tag.Value?.ToString();
break;
}
}

if (accessTokenIssued)
{
switch (proofType)
{
case ProofType.None when accessTokenType == AccessTokenType.Jwt:
Interlocked.Increment(ref _jwtTokenIssued);
break;
case ProofType.None when accessTokenType == AccessTokenType.Reference:
Interlocked.Increment(ref _referenceTokenIssued);
break;
case ProofType.DPoP when accessTokenType == AccessTokenType.Jwt:
Interlocked.Increment(ref _jwtPoPDPoPTokenIssued);
break;
case ProofType.DPoP when accessTokenType == AccessTokenType.Reference:
Interlocked.Increment(ref _referencePoPDPoPTokenIssued);
break;
case ProofType.ClientCertificate when accessTokenType == AccessTokenType.Jwt:
Interlocked.Increment(ref _jwtPoPmTLSTokenIssued);
break;
case ProofType.ClientCertificate when accessTokenType == AccessTokenType.Reference:
Interlocked.Increment(ref _referencePoPmTLSTokenIssued);
break;
}
}

if (refreshTokenIssued)
{
Interlocked.Increment(ref _refreshTokenIssued);
}

if (identityTokenIssued)
{
Interlocked.Increment(ref _idTokenIssued);
}

var tokenWasIssued = accessTokenIssued || refreshTokenIssued || identityTokenIssued;
if (!tokenWasIssued || string.IsNullOrEmpty(grantType))
{
return;
}

switch (grantType)
{
case GrantType.Implicit:
Interlocked.Increment(ref _implicitGrantTypeFlows);
break;
case GrantType.Hybrid:
Interlocked.Increment(ref _hybridGrantTypeFlows);
break;
case GrantType.AuthorizationCode:
Interlocked.Increment(ref _authorizationCodeGrantTypeFlows);
break;
case GrantType.ClientCredentials:
Interlocked.Increment(ref _clientCredentialsGrantTypeFlows);
break;
case GrantType.ResourceOwnerPassword:
Interlocked.Increment(ref _resourceOwnerPasswordGrantTypeFlows);
break;
case GrantType.DeviceFlow:
Interlocked.Increment(ref _deviceFlowGrantTypeFlows);
break;
default:
Interlocked.Increment(ref _otherGrantTypeFlows);
break;
}
}
}
34 changes: 34 additions & 0 deletions identity-server/src/Telemetry/Telemetry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


using System.Diagnostics.Metrics;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Validation;

namespace Duende.IdentityServer;
Expand Down Expand Up @@ -65,6 +66,11 @@ public static class Tags
public const string Scheme = "scheme";
public const string Result = "result";
public const string Type = "type";
public const string AccessTokenIssued = "access_token_issued";
public const string AccessTokenType = "access_token_type";
public const string RefreshTokenIssued = "refresh_token_issued";
public const string ProofType = "proof_type";
public const string IdTokenIssued = "id_token_issued";
}

/// <summary>
Expand Down Expand Up @@ -436,12 +442,14 @@ public static void RevocationFailure(string clientId, string error)
public static readonly Counter<long> TokenIssuedCounter =
Meter.CreateCounter<long>(Counters.TokenIssued);


/// <summary>
/// Helper method to increase <see cref="TokenIssuedCounter"/>
/// </summary>
/// <param name="clientId">Client Id</param>
/// <param name="grantType">Grant Type</param>
/// <param name="requestType">Type of authorization request</param>
[Obsolete("This overload will be removed in a future version. Use the overload with accessTokenIssued, accessTokenType, refreshTokenIssued, proofType, and idTokenIssued parameters instead.")]
public static void TokenIssued(string clientId, string grantType, AuthorizeRequestType? requestType)
{
Success(clientId);
Expand All @@ -451,6 +459,32 @@ public static void TokenIssued(string clientId, string grantType, AuthorizeReque
new(Tags.AuthorizeRequestType, requestType));
}

/// <summary>
/// Helper method to increase <see cref="TokenIssuedCounter"/>
/// </summary>
/// <param name="clientId">Client Id</param>
/// <param name="grantType">Grant Type</param>
/// <param name="requestType">Type of authorization request</param>
/// <param name="accessTokenIssued">Whether an access token was issued</param>
/// <param name="accessTokenType">The type of access token issued (Null if no access token was issued, otherwise JWT or Reference)</param>
/// <param name="refreshTokenIssued">Whether a refresh token was issued</param>
/// <param name="proofType">The proof type used (None, ClientCertificate, or DPoP)</param>
/// <param name="idTokenIssued">Whether an id token was issued</param>
public static void TokenIssued(string clientId, string grantType, AuthorizeRequestType? requestType,
bool accessTokenIssued, AccessTokenType? accessTokenType, bool refreshTokenIssued, ProofType proofType, bool idTokenIssued)
{
Success(clientId);
TokenIssuedCounter.Add(1,
new(Tags.Client, clientId),
new(Tags.GrantType, grantType),
new(Tags.AuthorizeRequestType, requestType),
new(Tags.AccessTokenIssued, accessTokenIssued),
new(Tags.AccessTokenType, accessTokenType),
new(Tags.RefreshTokenIssued, refreshTokenIssued),
new(Tags.ProofType, proofType),
new(Tags.IdTokenIssued, idTokenIssued));
}

/// <summary>
/// Helper method to increase <see cref="TokenIssuedCounter"/> on errors
/// </summary>
Expand Down
Loading
Loading