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

  1. Use a confidential (web/API) app with a configured client secret for introspection.
  2. Create a dedicated API/web app for backend introspection and use its credentials.
  3. 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

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


AI-assisted analysis of zitadel/zitadel@13948f2bcd (2026-09-06). Data as JSON: /api/errors/18ad68f1b97e13bd. Report an issue: GitHub.