Skip to content
Introducing the next era of Duende IdentityServer. Read our CEO’s announcement

OAuth 2.0 Introspection Events

The OAuth2IntrospectionEvents class exposes four Func delegates that let you hook into the introspection handler’s authentication pipeline. Each delegate receives a typed context object that carries the relevant data for that stage.

You configure events when registering the handler:

Program.cs
builder.Services.AddAuthentication(OAuth2IntrospectionDefaults.AuthenticationScheme)
.AddOAuth2Introspection(options =>
{
options.Authority = "https://demo.duendesoftware.com";
options.ClientId = "api";
options.ClientSecret = "secret";
options.Events = new OAuth2IntrospectionEvents
{
OnTokenValidated = context =>
{
// custom logic here
return Task.CompletedTask;
}
};
});
EventContext ClassTriggered When
OnTokenValidatedTokenValidatedContextToken passes validation; a ClaimsIdentity has been generated
OnAuthenticationFailedAuthenticationFailedContextAn exception is thrown during request processing
OnUpdateClientAssertionUpdateClientAssertionContextThe handler needs to refresh the client assertion
OnSendingRequestSendingRequestContextAn introspection request is about to be sent to the endpoint

Context: TokenValidatedContext

Invoked after the introspection endpoint returns an active token and a ClaimsIdentity has been generated. Use this event to add, remove, or transform claims before the identity is set on the HTTP context.

PropertyTypeDescription
SecurityTokenstringThe raw token that was introspected
PrincipalClaimsPrincipalThe generated principal (set or replace to modify identity)
PropertiesAuthenticationPropertiesAuthentication properties for the current request
options.Events = new OAuth2IntrospectionEvents
{
OnTokenValidated = context =>
{
var claims = new List<Claim>
{
new Claim("app_version", "2.0"),
};
var appIdentity = new ClaimsIdentity(claims);
context.Principal!.AddIdentity(appIdentity);
return Task.CompletedTask;
}
};
options.Events = new OAuth2IntrospectionEvents
{
OnTokenValidated = async context =>
{
var userService = context.HttpContext.RequestServices
.GetRequiredService<IUserService>();
var subject = context.Principal!.FindFirst("sub")?.Value;
if (subject is not null)
{
var roles = await userService.GetRolesAsync(subject);
var claims = roles.Select(r => new Claim(ClaimTypes.Role, r));
context.Principal.AddIdentity(new ClaimsIdentity(claims));
}
}
};

Context: AuthenticationFailedContext

Invoked when an exception is thrown during request processing. The exception will be re-thrown after this event unless you suppress it by calling context.Fail(), context.NoResult(), or context.Success().

PropertyTypeDescription
ErrorstringA description of the error that occurred
ExceptionExceptionThe exception that was thrown
options.Events = new OAuth2IntrospectionEvents
{
OnAuthenticationFailed = context =>
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>();
logger.LogWarning(
context.Exception,
"Token introspection authentication failed: {Error}",
context.Error);
return Task.CompletedTask;
}
};
options.Events = new OAuth2IntrospectionEvents
{
OnAuthenticationFailed = context =>
{
// Gracefully handle transient connectivity errors
// instead of throwing an exception to the caller
if (context.Exception is HttpRequestException)
{
context.NoResult();
}
return Task.CompletedTask;
}
};

Context: UpdateClientAssertionContext

Invoked when the introspection handler needs to authenticate to the introspection endpoint using a JWT client assertion rather than a shared secret. Use this event to generate and rotate short-lived signed JWTs for client authentication.

PropertyTypeDescription
ClientAssertionClientAssertionSet this to provide the assertion JWT and its type
ClientAssertionExpirationTimeDateTimeSet the expiry so the handler knows when to refresh
options.Events = new OAuth2IntrospectionEvents
{
OnUpdateClientAssertion = context =>
{
var keyService = context.HttpContext.RequestServices
.GetRequiredService<IClientAssertionSigningKeyService>();
var now = DateTime.UtcNow;
var expiry = now.AddMinutes(5);
var token = keyService.CreateSignedJwt(
issuer: context.Options.ClientId,
audience: context.Options.IntrospectionEndpoint ?? context.Options.Authority,
expiry: expiry);
context.ClientAssertion = new ClientAssertion
{
Type = OidcConstants.ClientAssertionTypes.JwtBearer,
Value = token,
};
context.ClientAssertionExpirationTime = expiry;
return Task.CompletedTask;
}
};

Context: SendingRequestContext

Invoked immediately before the TokenIntrospectionRequest is sent to the introspection endpoint. Use this event to inspect or modify the outgoing request, for example to add custom parameters.

PropertyTypeDescription
TokenIntrospectionRequestTokenIntrospectionRequestThe request object that will be sent to the endpoint
options.Events = new OAuth2IntrospectionEvents
{
OnSendingRequest = context =>
{
// Add extra parameters to the introspection request
context.TokenIntrospectionRequest.Parameters.Add(
"resource", "https://api.example.com");
return Task.CompletedTask;
}
};
options.Events = new OAuth2IntrospectionEvents
{
OnSendingRequest = context =>
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>();
logger.LogDebug(
"Sending introspection request to {Address}",
context.TokenIntrospectionRequest.Address);
return Task.CompletedTask;
}
};