Summary
When auth.oidc.private-key-path is configured, the loadRsaKey helper in SecurityConfig builds the RSAKey with a random UUID keyID but no x5t (SHA-1 certificate thumbprint). Nimbus therefore sets kid in the JWT assertion header, but Entra ID matches registered certificates by x5t, not kid, causing every token exchange to fail with:
AADSTS700027: The certificate with identifier used to sign the client assertion is not registered on application.
Root Cause
SecurityConfig.loadRsaKey (the private_key_jwt path, SecurityConfig.java around line 583):
return new RSAKey.Builder(publicKey)
.privateKey((RSAPrivateKey) privateKey)
.keyID(UUID.randomUUID().toString()) // ← random kid, no x5t
.build();
MSAL (the Node.js reference implementation for this same app registration) explicitly passes a thumbprint alongside the private key — this is the x5t value Entra requires.
Expected Behaviour
When the X.509 certificate corresponding to the private key is available, its SHA-1 thumbprint should be computed and set as x5t on the RSAKey so Nimbus includes it in the JWT assertion header.
Proposed Fix
Add an optional cert-path field to OidcAuthConfig. When set, loadRsaKey loads the certificate, computes SHA-1(cert.getEncoded()), and passes it to RSAKey.Builder.x509CertThumbprint(Base64URL). Providers that match on kid (Keycloak, Dex) are unaffected — cert-path stays blank and behaviour is unchanged.
auth:
oidc:
private-key-path: /run/secrets/gitproxy-oidc-key.pem
cert-path: /run/secrets/gitproxy-oidc-cert.pem # new — required for Entra ID
Environment
- git-proxy-java
1.0.0-beta.3
- IdP: Entra ID (Azure AD) with
private_key_jwt client authentication
- Spring Security
NimbusJwtClientAuthenticationParametersConverter
Summary
When
auth.oidc.private-key-pathis configured, theloadRsaKeyhelper inSecurityConfigbuilds theRSAKeywith a random UUIDkeyIDbut nox5t(SHA-1 certificate thumbprint). Nimbus therefore setskidin the JWT assertion header, but Entra ID matches registered certificates byx5t, notkid, causing every token exchange to fail with:Root Cause
SecurityConfig.loadRsaKey(theprivate_key_jwtpath,SecurityConfig.javaaround line 583):MSAL (the Node.js reference implementation for this same app registration) explicitly passes a
thumbprintalongside the private key — this is thex5tvalue Entra requires.Expected Behaviour
When the X.509 certificate corresponding to the private key is available, its SHA-1 thumbprint should be computed and set as
x5ton theRSAKeyso Nimbus includes it in the JWT assertion header.Proposed Fix
Add an optional
cert-pathfield toOidcAuthConfig. When set,loadRsaKeyloads the certificate, computesSHA-1(cert.getEncoded()), and passes it toRSAKey.Builder.x509CertThumbprint(Base64URL). Providers that match onkid(Keycloak, Dex) are unaffected —cert-pathstays blank and behaviour is unchanged.Environment
1.0.0-beta.3private_key_jwtclient authenticationNimbusJwtClientAuthenticationParametersConverter