Skip to content

Commit 3fd26af

Browse files
im
1 parent 6d180f0 commit 3fd26af

1 file changed

Lines changed: 113 additions & 0 deletions

File tree

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
---
2+
title: Client Assertions
3+
description: How to use client assertions (private_key_jwt / client_secret_jwt) for client authentication in protocol requests.
4+
sidebar:
5+
order: 8
6+
label: Client Assertions
7+
---
8+
9+
Client assertions are an alternative to client secrets for authenticating
10+
confidential clients at token endpoints. Instead of sending a shared secret,
11+
the client creates a signed JWT (or SAML assertion) and includes it in the
12+
request. This is defined in
13+
[RFC 7523 — JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication](https://datatracker.ietf.org/doc/html/rfc7523)
14+
and is commonly known as the `private_key_jwt` or `client_secret_jwt`
15+
authentication methods defined in
16+
[OpenID Connect Core §9](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication).
17+
18+
All protocol request types that derive from `ProtocolRequest` expose two
19+
properties for setting client assertions: `ClientAssertion` and
20+
`ClientAssertionFactory`.
21+
22+
## ClientAssertion
23+
24+
The `ClientAssertion` property lets you attach a pre-built assertion to any
25+
protocol request. Set its `Type` and `Value` and they will be included as the
26+
`client_assertion_type` and `client_assertion` parameters:
27+
28+
```csharp
29+
var response = await client.RequestClientCredentialsTokenAsync(
30+
new ClientCredentialsTokenRequest
31+
{
32+
Address = "https://demo.duendesoftware.com/connect/token",
33+
ClientId = "client",
34+
35+
ClientAssertion =
36+
{
37+
Type = OidcConstants.ClientAssertionTypes.JwtBearer,
38+
Value = mySignedJwt
39+
},
40+
41+
ClientCredentialStyle = ClientCredentialStyle.PostBody
42+
});
43+
```
44+
45+
:::note
46+
When using a client assertion, set `ClientCredentialStyle` to
47+
`ClientCredentialStyle.PostBody`. Client assertions are not compatible with
48+
`AuthorizationHeader` style and an `InvalidOperationException` will be thrown if
49+
both are combined with a `ClientId`.
50+
:::
51+
52+
## ClientAssertionFactory
53+
54+
*Added in IdentityModel 7.2.0*
55+
56+
The `ClientAssertionFactory` property accepts a `Func<Task<ClientAssertion>>`
57+
— a factory function that creates a **fresh** `ClientAssertion` on demand. This
58+
was introduced to support scenarios where a protocol request may need to be
59+
**retried**, and each attempt requires a new assertion with unique `jti` and
60+
`iat` claims.
61+
62+
The primary motivating scenario is **DPoP** (Demonstrating Proof of Possession).
63+
When a DPoP token request receives a `use_dpop_nonce` error, the HTTP handler
64+
retries the request with an updated DPoP proof. If the client assertion were
65+
static, the server could reject the retry because it has already seen that
66+
assertion's `jti`. The factory solves this by generating a new assertion for
67+
each attempt.
68+
69+
```csharp
70+
var response = await client.RequestClientCredentialsTokenAsync(
71+
new ClientCredentialsTokenRequest
72+
{
73+
Address = "https://demo.duendesoftware.com/connect/token",
74+
ClientId = "client",
75+
76+
ClientAssertionFactory = () => Task.FromResult(new ClientAssertion
77+
{
78+
Type = OidcConstants.ClientAssertionTypes.JwtBearer,
79+
Value = CreateSignedJwt() // generates a fresh JWT each time
80+
}),
81+
82+
ClientCredentialStyle = ClientCredentialStyle.PostBody
83+
});
84+
```
85+
86+
When `ClientAssertionFactory` is set, the factory is stored on the
87+
`HttpRequestMessage.Options` so that DPoP retry handlers (and other delegating
88+
handlers in the pipeline) can invoke it to obtain a new assertion on each
89+
attempt.
90+
91+
:::note
92+
If both `ClientAssertion` and `ClientAssertionFactory` are set, the factory
93+
takes precedence during request preparation.
94+
:::
95+
96+
### Usage with Duende.IdentityModel.OidcClient
97+
98+
Both the `ClientAssertion` and `ClientAssertionFactory` properties exist on
99+
`ProtocolRequest` to support
100+
[Duende.IdentityModel.OidcClient](/identitymodel-oidcclient/). The OidcClient
101+
library builds on IdentityModel's protocol requests internally, and when
102+
configured with client assertion-based authentication, it sets these properties
103+
on the underlying requests it creates.
104+
105+
When `ClientAssertionFactory` is set, it is used during both:
106+
107+
- **Pushed Authorization Requests (PAR)** — the factory is invoked to produce a
108+
fresh assertion for the PAR endpoint request.
109+
- **Token requests** — the factory is invoked again to produce a fresh assertion
110+
for the token endpoint request.
111+
112+
This ensures each request carries its own unique assertion, which is essential
113+
when the authorization server enforces `jti` uniqueness across requests.

0 commit comments

Comments
 (0)