zitadel/zitadel · error · errNoClientSecret
client has no configured secret
Error message
client has no configured secret
What it means
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.
Source
Thrown at internal/api/oidc/introspect.go:137
NotBefore: oidc.FromTime(token.tokenCreation),
Audience: token.audience,
AuthenticationMethodsReferences: AuthMethodTypesToAMR(token.authMethods),
Issuer: op.IssuerFromContext(ctx),
JWTID: token.tokenID,
Actor: actorDomainToClaims(token.actor),
}
introspectionResp.SetUserInfo(userInfo)
return op.NewResponse(introspectionResp), nil
}
type introspectionClientResult struct {
clientID string
projectID string
projectRoleAssertion bool
err error
}
var errNoClientSecret = errors.New("client has no configured secret")
func (s *Server) introspectionClientAuth(ctx context.Context, cc *op.ClientCredentials, rc chan<- *introspectionClientResult) {
ctx, span := tracing.NewSpan(ctx)
clientID, projectID, projectRoleAssertion, err := func() (string, string, bool, error) {
client, err := s.clientFromCredentials(ctx, cc)
if err != nil {
return "", "", false, err
}
if cc.ClientAssertion != "" {
verifier := op.NewJWTProfileVerifierKeySet(keySetMap(client.PublicKeys), op.IssuerFromContext(ctx), time.Hour, time.Second)
if _, err := op.VerifyJWTAssertion(ctx, cc.ClientAssertion, verifier); err != nil {
return "", "", false, oidc.ErrUnauthorizedClient().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError)
}
return client.ClientID, client.ProjectID, client.ProjectRoleAssertion, nil
}View on GitHub (pinned to 13948f2bcd)
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.
Defensive patterns
Strategy: validation
Validate before calling
// only use confidential apps (with a secret) for introspection
if (app.type !== 'web' && app.type !== 'api') throw new Error('public/native apps cannot authenticate for introspection'); Try / catch
catch (e) { if (e.error === 'unauthorized_client') { checkClientCredentials(); } else throw e; } Prevention
- Use a dedicated confidential web/API app for backend token introspection.
- Verify the client secret exists and is current after resets.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- introspection client without app type
- invalid token format
- dynamic client registration not permitted
- no auth code provided
- unable to start oidc provider: %w
AI-assisted analysis of zitadel/zitadel@13948f2bcd (2026-09-06).
Data as JSON: /api/errors/18ad68f1b97e13bd.
Report an issue: GitHub.