Skip to content

Client Assertions

If your token client is using a client assertion instead of a shared secret, you can provide the assertion in two ways:

  • Use the request parameter mechanism to pass a client assertion to the management
  • Implement the IClientAssertionService interface to centralize client assertion creation

Here’s a sample client assertion service using the Microsoft JWT library:

ClientAssertionService.cs
using Duende.AccessTokenManagement;
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
public class ClientAssertionService(IOptionsSnapshot<ClientCredentialsClient> options)
: IClientAssertionService
{
public Task<ClientAssertion?> GetClientAssertionAsync(
ClientCredentialsClientName? clientName = null,
TokenRequestParameters? parameters = null,
CancellationToken ct = default)
{
if (clientName == "invoice")
{
var options1 = options.Get(clientName);
var descriptor = new SecurityTokenDescriptor
{
Issuer = options1.ClientId!.ToString(),
// Set the audience to the url of identity server. Do not use the tokenurl to build the autority.
Audience = "https://--url-to-authority-here--",
Expires = DateTime.UtcNow.AddMinutes(1),
SigningCredentials = GetSigningCredential(),
Claims = new Dictionary<string, object>
{
{ JwtClaimTypes.JwtId, Guid.NewGuid().ToString() },
{ JwtClaimTypes.Subject, options1.ClientId.ToString()! },
{ JwtClaimTypes.IssuedAt, DateTimeOffset.UtcNow.ToUnixTimeSeconds() }
},
AdditionalHeaderClaims = new Dictionary<string, object>
{
{ JwtClaimTypes.TokenType, "client-authentication+jwt" }
}
};
var handler = new JsonWebTokenHandler();
var jwt = handler.CreateToken(descriptor);
return Task.FromResult<ClientAssertion?>(new ClientAssertion
{
Type = OidcConstants.ClientAssertionTypes.JwtBearer,
Value = jwt
});
}
return Task.FromResult<ClientAssertion?>(null);
}
private SigningCredentials GetSigningCredential()
{
throw new NotImplementedException();
}
}

For a complete working example, see the WebClientAssertions sample.

When using AddOpenIdConnectAccessTokenManagement, the same IClientAssertionService is used to authenticate the client for the requests Duende.AccessTokenManagement makes itself, such as refreshing and revoking user tokens. The client name passed to GetClientAssertionAsync is derived from the OpenID Connect scheme name.

Duende.AccessTokenManagement also hooks into the ASP.NET Core OpenID Connect handler events and automatically adds the client assertion from your IClientAssertionService to the authorization code exchange and, on .NET 9 or newer, to the pushed authorization request (PAR).

Signed authorization requests (JAR, RFC 9101) are not created by the library. If your client needs to send a request object, sign it in your own event handler, as shown in Combining DPoP with custom OpenID Connect events.