Client Secret JWT (client_secret_jwt)
This sample demonstrates how to use Keycloak's "Signed JWT with Client Secret" client authentication method (client_secret_jwt).
Instead of sending the raw client secret over the wire, the app builds a short-lived HS256-signed JWT assertion and sends that to Keycloak's token endpoint. Keycloak verifies the signature using the same shared secret — the secret itself never leaves the application.
This satisfies compliance policies that prohibit transmitting client secrets over the network.
cs
namespace ClientSecretJwt;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Duende.AccessTokenManagement;
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Keycloak.AuthServices.Sdk;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
/// <summary>
/// Implements Duende's <see cref="IClientAssertionService"/> for the
/// <c>client_secret_jwt</c> authentication method (RFC 7523 / Keycloak "Signed JWT with Client Secret").
///
/// Instead of sending the shared secret over the wire, this service builds a short-lived
/// HS256-signed JWT (<c>client_assertion</c>). Keycloak verifies the signature using the
/// same shared secret it already holds — the secret itself never leaves the application.
/// </summary>
public sealed class ClientSecretJwtAssertionService(IOptions<KeycloakAdminClientOptions> options)
: IClientAssertionService
{
private readonly KeycloakAdminClientOptions _options = options.Value;
public Task<ClientAssertion?> GetClientAssertionAsync(
ClientCredentialsClientName? clientName = null,
TokenRequestParameters? parameters = null,
CancellationToken ct = default
)
{
var clientId = _options.Resource;
var tokenEndpoint = _options.KeycloakTokenEndpoint;
var secret = _options.Credentials.Secret;
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var now = DateTime.UtcNow;
var token = new JwtSecurityToken(
issuer: clientId,
audience: tokenEndpoint,
claims:
[
new Claim(JwtRegisteredClaimNames.Sub, clientId),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
],
notBefore: now,
expires: now.AddMinutes(1),
signingCredentials: credentials
);
return Task.FromResult<ClientAssertion?>(
new ClientAssertion
{
Type = OidcConstants.ClientAssertionTypes.JwtBearer,
Value = new JwtSecurityTokenHandler().WriteToken(token),
}
);
}
}See sample source code: keycloak-authorization-services-dotnet/tree/main/samples/ClientSecretJwt