Skip to content

Commit 9e18cb3

Browse files
authored
Add mTLS Bearer token design specification (#5848)
This PR adds app-level support for sending confidential client certificates over **mTLS transport** independently from token type. Today, `WithMtlsProofOfPossession()` effectively drives both: - the **token type** (`mTLS PoP`) - the **certificate transport path** (mTLS) That makes it hard to express the missing scenario: - **mTLS transport + Bearer token** This change introduces a new certificate option: ```csharp new CertificateOptions { SendCertificateOverMtls = true } ```
1 parent a0e5bf6 commit 9e18cb3

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Mini Spec: mTLS Bearer for Confidential Client Certificates
2+
3+
## Decision
4+
5+
Add an app-level certificate transport option:
6+
7+
```csharp
8+
public record CertificateOptions
9+
{
10+
public bool SendX5C { get; init; } = false;
11+
public bool AssociateTokensWithCertificate { get; init; } = false;
12+
13+
// New
14+
public bool SendCertificateOverMtls { get; init; } = false;
15+
}
16+
```
17+
18+
## Intent
19+
20+
Separate these two concerns:
21+
22+
1. **Certificate transport**
23+
- JWT client assertion in request body
24+
- TLS client certificate over mTLS
25+
26+
2. **Token type**
27+
- Bearer
28+
- mTLS PoP
29+
30+
## Rule
31+
32+
`WithMtlsProofOfPossession()` is a request-level requirement and **implies mTLS transport**.
33+
34+
`SendCertificateOverMtls` is only the **default transport setting** for requests that do not explicitly request PoP.
35+
36+
## Effective Behavior
37+
38+
### App-level default
39+
- `SendCertificateOverMtls = false`
40+
- default to assertion-based certificate auth
41+
- `SendCertificateOverMtls = true`
42+
- default to mTLS certificate transport
43+
44+
### Request-level override
45+
- `.WithMtlsProofOfPossession()`
46+
- always use mTLS transport
47+
- request mTLS PoP token type
48+
49+
## Behavior Matrix
50+
51+
| App `SendCertificateOverMtls` | Request `.WithMtlsProofOfPossession()` | Effective Certificate Transport | Token Type | Result |
52+
|---|---:|---|---|---|
53+
| false | No | Request body assertion | Bearer | Existing |
54+
| true | No | mTLS handshake | Bearer | New |
55+
| false | Yes | mTLS handshake | mTLS PoP | Existing / preserved |
56+
| true | Yes | mTLS handshake | mTLS PoP | Existing / preserved |
57+
58+
## Why no throw when `SendCertificateOverMtls` is set to `false` for mTLS PoP?
59+
60+
Because `false` does **not** mean “mTLS forbidden.”
61+
It only means “do not use mTLS by default.”
62+
63+
If the request explicitly asks for PoP, request-level semantics win.
64+
65+
## Validation
66+
67+
### Valid
68+
- Certificate + default options + Bearer
69+
- Certificate + `SendCertificateOverMtls=true` + Bearer
70+
- Certificate + PoP
71+
- Certificate + `SendCertificateOverMtls=true` + PoP
72+
73+
### Invalid
74+
- Secret credential + `SendCertificateOverMtls=true`
75+
- Signed assertion string/delegate + `SendCertificateOverMtls=true`
76+
- `.WithMtlsProofOfPossession()` without a certificate credential
77+
78+
## Implementation Notes
79+
80+
### 1. Public API
81+
Add `SendCertificateOverMtls` to `CertificateOptions`.
82+
83+
### 2. Builder/config
84+
Flow `SendCertificateOverMtls` through:
85+
- `ConfidentialClientApplicationBuilder.WithCertificate(X509Certificate2, CertificateOptions)`
86+
- callback-based `WithCertificate(..., CertificateOptions)` overloads
87+
88+
### 3. Resolver
89+
Compute:
90+
91+
```csharp
92+
bool useMtlsTransport =
93+
request.IsMtlsPopRequested ||
94+
appConfig.SendCertificateOverMtls;
95+
```
96+
97+
### 4. Credential material
98+
For certificate credentials:
99+
100+
- if `useMtlsTransport == true`
101+
- return empty body auth params
102+
- return resolved certificate for HTTP/TLS layer
103+
104+
- else
105+
- return client assertion in request body
106+
107+
### 5. Token client
108+
No semantic change beyond honoring the resolved certificate when `useMtlsTransport == true`.
109+
110+
## Non-Goals
111+
112+
- No new `.WithBoundBearer()` API
113+
- No breaking change to existing PoP behavior
114+
- No cache behavior changes
115+
116+
## Tests
117+
118+
### Unit
119+
- `SendCertificateOverMtls` defaults to false
120+
- builder stores the new option
121+
- PoP forces mTLS even when app option is false
122+
- bearer + `SendCertificateOverMtls=true` uses mTLS
123+
- unsupported credential types fail in mTLS transport mode
124+
125+
### Integration
126+
- SNI Bearer still works
127+
- mTLS Bearer works
128+
- PoP still works without setting `SendCertificateOverMtls`
129+
- PoP also works when `SendCertificateOverMtls=true`

0 commit comments

Comments
 (0)