Skip to content

Support certificate and federated client credentials for OIDC providers - #167

Merged
woksin merged 10 commits into
mainfrom
feature/149-oidc-client-credentials
Oct 2, 2026
Merged

woksin merged 10 commits into
mainfrom
feature/149-oidc-client-credentials

Conversation

@woksin

@woksin woksin commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Added

An OIDC provider can now authenticate AuthProxy to its token endpoint with a
private_key_jwt client assertion signed by a certificate (file, certificate
store or Azure Key Vault), or with a federated token (workload identity token
file or Azure managed identity), instead of a client secret. The credential
loaders come from Microsoft.Identity.Web; ClientSecret stays the default and
configuring both fails at startup.
@woksin woksin self-assigned this Oct 1, 2026
@woksin woksin added the minor label Oct 1, 2026
@woksin

woksin commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Notes for reviewers (not part of the release note):

Design decisions

  • Credential loading reuses Microsoft.Identity.Web's loaders (Microsoft.Identity.Web.Certificate 4.15.0: DefaultCredentialsLoader, ManagedIdentityClientAssertion, AzureIdentityForKubernetesClientAssertion) instead of hand-rolled Key Vault / IMDS / token-file code. The configuration surface is an AuthProxy-owned OidcClientCredential (one Source enum plus the properties that source needs) mapped onto CredentialDescription. This keeps AuthProxy's public configuration small and avoids exposing third-party types or unsupported sources such as CustomSignedAssertion.
  • The client assertion is hooked into the standard ASP.NET Core OpenIdConnect events: OnAuthorizationCodeReceived (code redemption) and OnPushAuthorization (PAR, which .NET 9+ uses when the provider advertises it, via HandleClientAuthentication()). client_secret is removed from those requests.
  • The certificate assertion follows RFC 7523 and OIDC Core section 9: iss and sub are the client ID, aud is the discovered token endpoint, it has a fresh jti, a 5-minute lifetime, and an x5t header. It is signed RS256 for RSA keys or ES256 for ECDSA keys.
  • A conservative choice: if both ClientSecret and a non-secret ClientCredential are configured, startup fails instead of silently preferring one. Required properties are also validated at startup, including the AZURE_FEDERATED_TOKEN_FILE fallback.
  • Rotation: a loaded certificate is reloaded once it has expired. Federated assertion providers cache and refresh themselves.
  • Scope: OAuth 2.0 (non-OIDC) providers and the Aspire WithOidcProvider helper still take a client secret only. Either can be a follow-up if wanted.
  • New transitive dependencies: Azure.Identity, Azure.Security.KeyVault.Certificates/Secrets and MSAL. dotnet list package --vulnerable --include-transitive reports none.

Local gate (mirrors CI): dotnet build -c Debug ✅, dotnet build -c Release ✅ (0 warnings), dotnet test -c Debug --no-build ✅ (Aspire 74, Security 246, AuthProxy 2204), security specs -c Release ✅, vulnerable-package check ✅, dotnet publish -c Release ✅. I did not build the Docker image locally.

Not exercised against a live identity provider: Key Vault download and the managed identity token (IMDS) need Azure. The certificate-file and federated-token-file paths run through the real Microsoft.Identity.Web loader in specs.

This is security-sensitive and needs a cross-provider review before merge.

@woksin

woksin commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Merged current origin/main (e5e0f84) into this branch without rebasing.

Conflicted files and resolutions:

  • Directory.Packages.props: retained Microsoft.Identity.Web.Certificate 4.15.0 for OIDC credentials and all incoming Data Protection dependencies: Redis 10.0.12, Azure Blob 1.5.4, Key Vault keys 1.6.4 and Azure.Identity 1.21.0.
  • Source/AuthProxy/AuthProxy.csproj: retained both the certificate credential package reference and the Redis Data Protection package reference, alongside the incoming Azure references.

The fail-closed identity verification from #164, key stores from #168 and host/path routing from #169 were kept unchanged from main; the remaining branch diff contains only #167's OIDC credentials and their documentation/specs. The confirmed whitespace-only PFX-password finding was already fixed in bf7ca95, with a certificate-loading/signature-validation regression spec, and that fix remains intact.

Local checks (targeted only, as requested):

  • dotnet build Source/AuthProxy/AuthProxy.csproj --configuration Release -warnaserror: passed with analyzers enabled, zero warnings/errors.
  • dotnet build Source/AuthProxy.Specs/AuthProxy.Specs.csproj --configuration Release -warnaserror: passed with analyzers enabled, zero warnings/errors.
  • dotnet test Source/AuthProxy.Specs/AuthProxy.Specs.csproj --configuration Debug -warnaserror: skipped because pi-phase admission timed out after 120 seconds (exit 75); the command did not start and was not retried.
  • git diff --check: passed.

No whole-solution local gate was run. The PR's CI is the full gate; it will not be watched or waited on in this task. The release-note body was checked against the post-merge diff and updated without including changes already on main. No labels were changed and the PR was not merged.

@woksin
woksin merged commit 6fe754f into main Oct 2, 2026
@woksin
woksin deleted the feature/149-oidc-client-credentials branch October 2, 2026 00:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant