{"record":{"id":"4a619a1db2ecdcbe","repo":"git-ecosystem/git-credential-manager","slug":"service-principal-sp-id-must-have-either-a-cer","errorCode":null,"errorMessage":"Service principal '{sp.Id}' must have either a certificate or client secret.","messagePattern":"Service principal '(.+?)' must have either a certificate or client secret\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Core/Authentication/Entra/EntraAuthentication.ConfidentialClient.cs","lineNumber":33,"sourceCode":"        Context.Trace.WriteLine($\"Creating confidential client for service principal '{sp.Id}' in tenant '{sp.TenantId}'...\");\n        var builder = ConfidentialClientApplicationBuilder.Create(sp.Id)\n            .WithTenantId(sp.TenantId)\n            .WithHttpClientFactory(_httpFactory)\n            .WithTraceLogging(Context);\n\n        if (sp.Certificate is not null)\n        {\n            Context.Trace.WriteLine($\"Using service principal certificate: {sp.Certificate.Thumbprint}\");\n            builder.WithCertificate(sp.Certificate);\n        }\n        else if (!string.IsNullOrWhiteSpace(sp.ClientSecret))\n        {\n            Context.Trace.WriteLineSecrets(\"Using service principal secret: {0}\", [sp.ClientSecret]);\n            builder.WithClientSecret(sp.ClientSecret);\n        }\n        else\n        {\n            throw new ArgumentException($\"Service principal '{sp.Id}' must have either a certificate or client secret.\", nameof(sp));\n        }\n\n        Context.Trace.WriteLine($\"SendX5C is '{sp.SendX5C}'\");\n\n        IConfidentialClientApplication app = builder.Build();\n        await RegisterCacheAsync(app);\n\n        Context.Trace.WriteLine($\"Acquiring token for service principal with scopes '{string.Join(\", \", scopes)}'...\");\n        AuthenticationResult result = await app.AcquireTokenForClient(scopes)\n            .WithSendX5C(sp.SendX5C)\n            .ExecuteAsync(ct);\n\n        return AuthResult.FromMsalResult(result);\n    }\n\n    public async Task<IEntraAuthenticationResult> GetTokenForManagedIdentityAsync(\n        string resource, ManagedIdentity mi, CancellationToken ct = default)\n    {","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/git-ecosystem/git-credential-manager/blob/e8ce762cd04b4100ae637b5fbf39ef9d0a96561e/src/Core/Authentication/Entra/EntraAuthentication.ConfidentialClient.cs#L15-L51","documentation":"When authenticating an Entra ID service principal via a confidential client, MSAL requires a credential: either a client certificate or a client secret. This guard fires when the service-principal record has neither, making token acquisition impossible.","triggerScenarios":"GetTokenForServicePrincipalAsync builds a ConfidentialClientApplication; sp.ClientCertificate is null so the secret branch runs, but sp.ClientSecret is also null/empty, hitting the ArgumentException before builder.Build().","commonSituations":"Service principal created in Azure without generating a client secret; secret expired and removed; certificate never uploaded; configuration file/helper returning an SP record with credentials omitted (e.g. missing GCM_SECRET or cert settings).","solutions":["Create a client secret for the service principal (az ad sp create-password ... or Azure portal) and configure it","Or upload a certificate to the SP and configure GCM to use the certificate","Verify the credential source (env var/config/helper) is actually populated where GCM reads it","Confirm you are targeting the correct app/SP ID — a wrong ID can look credential-less"],"exampleFix":"# before: SP with no credential configured\naz ad sp create --id ...   # no secret/cert\n# after\naz ad sp credential reset --id <sp-id>   # generates a client secret, configure it for GCM","handlingStrategy":"validation","validationCode":"if (string.IsNullOrEmpty(sp.ClientSecret) && sp.ClientCertificate is null)\n    throw new ArgumentException(\"Service principal needs a client secret or certificate before authentication.\", nameof(sp));","typeGuard":"bool HasCredential(ServicePrincipal sp) => !string.IsNullOrEmpty(sp.ClientSecret) || sp.ClientCertificate is not null;","tryCatchPattern":"try { /* SP auth */ }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"must have either a certificate or client secret\")) { /* guide user to create a credential */ }","preventionTips":["Always reset/assign a secret or certificate when creating a service principal","Rotate and store SP credentials in a secret manager rather than config files","Validate the SP record before invoking GCM"],"tags":["azure","service-principal","missing-secret","msal"],"backgroundTag":"missing-credentials","analyzedSha":"e8ce762cd04b4100ae637b5fbf39ef9d0a96561e","analyzedAt":"2026-09-11T17:15:08.753Z","contentChangedAt":"2026-09-11T17:15:08.753Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}