-
Notifications
You must be signed in to change notification settings - Fork 380
Add draft spec for WithCertificate API #5587
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
gladjohn
wants to merge
1
commit into
main
Choose a base branch
from
gladjohn-cert-spec
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| # Draft Spec: `WithCertificate` API – Bearer, mTLS, Dynamic Selection | ||
|
|
||
| ## 1. Goals | ||
|
|
||
| - Keep existing `WithCertificate(X509Certificate2, bool sendX5C)` behavior **unchanged**. | ||
| - Support: | ||
| - Standard **Bearer** client‑credential tokens. | ||
| - **mTLS Bearer** (certificate used only for TLS binding). | ||
| - **mTLS PoP** (existing `.WithMtlsProofOfPossession()` flow). | ||
| - Add a **delegate‑based** `WithCertificate` that can select a certificate **per request** using `IAppConfig`. | ||
|
|
||
| ## 2. New types | ||
|
|
||
| ### 2.1 `ClientCertificateUsage` | ||
|
|
||
| ```csharp | ||
| public enum ClientCertificateUsage | ||
| { | ||
| /// Certificate is used to produce a client assertion (private_key_jwt). | ||
| Assertion, | ||
|
|
||
| /// Certificate is used for mutual TLS (client TLS authentication / binding). | ||
| Mtls | ||
| } | ||
| ``` | ||
|
|
||
| ### 2.2 `CertificateConfig` | ||
|
|
||
| ```csharp | ||
| public sealed class CertificateConfig | ||
| { | ||
| public X509Certificate2 Certificate { get; init; } = default!; | ||
| public ClientCertificateUsage Usage { get; init; } = ClientCertificateUsage.Assertion; | ||
| public bool SendX5C { get; init; } = false; | ||
| } | ||
| ``` | ||
|
|
||
| - `Usage = Assertion` → certificate is used for client assertion (current behavior). | ||
| - `Usage = Mtls` → certificate is used for client TLS authentication and exposed as `BindingCertificate`. | ||
| - `SendX5C` matters only for assertion usage. | ||
|
|
||
| ## 3. `WithCertificate` overloads | ||
|
|
||
| ### 3.1 Existing overloads (unchanged) | ||
|
|
||
| ```csharp | ||
| public ConfidentialClientApplicationBuilder WithCertificate(X509Certificate2 certificate); | ||
| public ConfidentialClientApplicationBuilder WithCertificate( | ||
| X509Certificate2 certificate, | ||
| bool sendX5C); | ||
| ``` | ||
|
|
||
| **Internal mapping:** | ||
|
|
||
| ```csharp | ||
| WithCertificate(new CertificateConfig | ||
| { | ||
| Certificate = certificate, | ||
| Usage = ClientCertificateUsage.Assertion, | ||
| SendX5C = sendX5COrFalse | ||
| }); | ||
| ``` | ||
|
|
||
| This guarantees full backward compatibility. | ||
|
|
||
| ### 3.2 New static overload | ||
|
|
||
| ```csharp | ||
| public ConfidentialClientApplicationBuilder WithCertificate( | ||
| CertificateConfig config); | ||
| ``` | ||
|
|
||
| - `Usage = Assertion` | ||
| → sets the “client assertion certificate” and `SendX5C`. | ||
| - `Usage = Mtls` | ||
| → sets the “mTLS certificate” used in TLS and as `AuthenticationResult.BindingCertificate`. | ||
|
|
||
| ### 3.3 New dynamic overloads (per‑request selection) | ||
|
|
||
| ```csharp | ||
| // Full-featured: caller can choose Assertion vs Mtls dynamically. | ||
| public ConfidentialClientApplicationBuilder WithCertificate( | ||
| Func<IAppConfig, CertificateConfig> selector); | ||
|
|
||
| // Convenience overload: simplest dynamic form (Assertion by default). | ||
| public ConfidentialClientApplicationBuilder WithCertificate( | ||
| Func<IAppConfig, X509Certificate2> selector); | ||
| ``` | ||
|
|
||
| **Semantics** | ||
|
|
||
| - Selectors are stored in configuration and **evaluated once per `ExecuteAsync()` call**. | ||
| - MSAL builds an `IAppConfig` view (ClientId, TenantId / authority, etc.) and passes it to the selector. | ||
| - The selected certificate/config: | ||
| - Drives assertion and/or mTLS behavior as per `CertificateConfig.Usage`. | ||
| - Is also surfaced as `appConfig.ClientCredentialCertificate` so retry/observer hooks can log success/failure for the right certificate. | ||
|
|
||
| The simple delegate overload behaves as: | ||
|
|
||
| ```csharp | ||
| WithCertificate(ac => new CertificateConfig | ||
| { | ||
| Certificate = selector(ac), | ||
| Usage = ClientCertificateUsage.Assertion | ||
| }); | ||
| ``` | ||
|
|
||
| ## 4. Bearer vs mTLS‑PoP (request‑level choice) | ||
|
|
||
| Token type remains a **per‑request** decision: | ||
|
|
||
| ```csharp | ||
| // Default: Bearer | ||
| await app.AcquireTokenForClient(scopes).ExecuteAsync(); | ||
|
|
||
| // mTLS PoP | ||
| await app.AcquireTokenForClient(scopes) | ||
| .WithMtlsProofOfPossession() | ||
| .ExecuteAsync(); | ||
| ``` | ||
|
|
||
| Interaction with `ClientCertificateUsage`: | ||
|
|
||
| - `Usage = Assertion`, no `.WithMtlsProofOfPossession()` | ||
| → standard Bearer client‑credential flow (current behavior). | ||
|
|
||
| - `Usage = Mtls`, no `.WithMtlsProofOfPossession()` | ||
| → **mTLS Bearer**: | ||
| - TLS client certificate set from the mTLS cert. | ||
| - `AuthenticationResult.TokenType == "Bearer"`. | ||
| - `AuthenticationResult.BindingCertificate` = mTLS cert. | ||
|
|
||
| - `Usage = Mtls` + `.WithMtlsProofOfPossession()` | ||
| → **mTLS PoP** (current PoP semantics). | ||
|
|
||
| ## 5. Example usage | ||
|
|
||
| ### 5.1 Static assertion (legacy) | ||
|
|
||
| ```csharp | ||
| var app = ConfidentialClientApplicationBuilder | ||
| .Create(clientId) | ||
| .WithAuthority(authority) | ||
| .WithCertificate(assertionCert) | ||
| .Build(); | ||
|
|
||
| var result = await app.AcquireTokenForClient(scopes).ExecuteAsync(); | ||
| ``` | ||
|
|
||
| ### 5.2 Static mTLS Bearer / mTLS PoP | ||
|
|
||
| ```csharp | ||
| var app = ConfidentialClientApplicationBuilder | ||
| .Create(clientId) | ||
| .WithAuthority(authority) | ||
| .WithCertificate(new CertificateConfig | ||
| { | ||
| Certificate = mtlsCert, | ||
| Usage = ClientCertificateUsage.Mtls, | ||
| SendX5C = true | ||
| }) | ||
| .Build(); | ||
|
|
||
| // mTLS Bearer | ||
| var bearer = await app.AcquireTokenForClient(scopes).ExecuteAsync(); | ||
|
|
||
| // mTLS PoP | ||
| var pop = await app.AcquireTokenForClient(scopes) | ||
| .WithMtlsProofOfPossession() | ||
| .ExecuteAsync(); | ||
| ``` | ||
|
|
||
| ### 5.3 Dynamic certificate selection | ||
|
|
||
| ```csharp | ||
| var app = ConfidentialClientApplicationBuilder | ||
| .Create(clientId) | ||
| .WithAuthority(authority) | ||
| .WithCertificate(appConfig => | ||
| new CertificateConfig | ||
| { | ||
| Certificate = selectionService | ||
| .GetCertificate(appConfig.ClientId, appConfig.TenantId), | ||
| Usage = ClientCertificateUsage.Mtls, | ||
| SendX5C = true | ||
| }) | ||
| .Build(); | ||
| ``` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
THere is no need for this? It should be derived from the context?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you suggesting we add a a request‑level WithMutualTls() to opt into Bearer over mTLS?