{"record":{"id":"18ad68f1b97e13bd","repo":"zitadel/zitadel","slug":"client-has-no-configured-secret","errorCode":null,"errorMessage":"client has no configured secret","messagePattern":"client has no configured secret","errorType":"error_code","errorClass":"errNoClientSecret","httpStatus":401,"severity":"error","filePath":"internal/api/oidc/introspect.go","lineNumber":137,"sourceCode":"\t\tNotBefore:                       oidc.FromTime(token.tokenCreation),\n\t\tAudience:                        token.audience,\n\t\tAuthenticationMethodsReferences: AuthMethodTypesToAMR(token.authMethods),\n\t\tIssuer:                          op.IssuerFromContext(ctx),\n\t\tJWTID:                           token.tokenID,\n\t\tActor:                           actorDomainToClaims(token.actor),\n\t}\n\tintrospectionResp.SetUserInfo(userInfo)\n\treturn op.NewResponse(introspectionResp), nil\n}\n\ntype introspectionClientResult struct {\n\tclientID             string\n\tprojectID            string\n\tprojectRoleAssertion bool\n\terr                  error\n}\n\nvar errNoClientSecret = errors.New(\"client has no configured secret\")\n\nfunc (s *Server) introspectionClientAuth(ctx context.Context, cc *op.ClientCredentials, rc chan<- *introspectionClientResult) {\n\tctx, span := tracing.NewSpan(ctx)\n\n\tclientID, projectID, projectRoleAssertion, err := func() (string, string, bool, error) {\n\t\tclient, err := s.clientFromCredentials(ctx, cc)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", false, err\n\t\t}\n\n\t\tif cc.ClientAssertion != \"\" {\n\t\t\tverifier := op.NewJWTProfileVerifierKeySet(keySetMap(client.PublicKeys), op.IssuerFromContext(ctx), time.Hour, time.Second)\n\t\t\tif _, err := op.VerifyJWTAssertion(ctx, cc.ClientAssertion, verifier); err != nil {\n\t\t\t\treturn \"\", \"\", false, oidc.ErrUnauthorizedClient().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError)\n\t\t\t}\n\t\t\treturn client.ClientID, client.ProjectID, client.ProjectRoleAssertion, nil\n\n\t\t}","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/zitadel/zitadel/blob/13948f2bcd6f257794dbd6d342c2ac30bc88fe54/internal/api/oidc/introspect.go#L119-L155","documentation":"During introspection endpoint client authentication, errNoClientSecret signals the client app has no configured secret (e.g. it's a native/public app using PKCE). It's wrapped in ErrUnauthorizedClient so the caller receives an unauthorized_client error.","triggerScenarios":"Calling the /oauth/v2/introspection endpoint with client credentials of an app that has no client secret — typically public (native) apps that never had a secret configured.","commonSituations":"Trying to use a SPA/native app's client_id+secret to introspect tokens, or an app whose secret was removed/reset in the console while integrations still use it.","solutions":["Use a confidential (web/API) app with a configured client secret for introspection.","Create a dedicated API/web app for backend introspection and use its credentials.","If the app should be confidential, set/generate its client secret in the console."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// only use confidential apps (with a secret) for introspection\nif (app.type !== 'web' && app.type !== 'api') throw new Error('public/native apps cannot authenticate for introspection');","typeGuard":null,"tryCatchPattern":"catch (e) { if (e.error === 'unauthorized_client') { checkClientCredentials(); } else throw e; }","preventionTips":["Use a dedicated confidential web/API app for backend token introspection.","Verify the client secret exists and is current after resets."],"tags":["oidc","introspection","client-auth"],"backgroundTag":"missing-credentials","analyzedSha":"13948f2bcd6f257794dbd6d342c2ac30bc88fe54","analyzedAt":"2026-09-06T10:16:19.814Z","contentChangedAt":"2026-09-06T10:16:19.814Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}