gravitational/teleport · warning

credential not found

Error message

credential not found

What it means

Touch ID credential lookup returns ErrCredentialNotFound when the platform authenticator has no matching credential for the requested user/relying party — e.g. enumerateCredentials returns zero results or pickCredential finds nothing. It is a sentinel error so callers (like tsh) can fall back to another MFA method.

Source

Thrown at lib/auth/touchid/api.go:51

	"slices"
	"sort"
	"sync"
	"sync/atomic"
	"time"

	"github.com/fxamacker/cbor/v2"
	"github.com/go-webauthn/webauthn/protocol"
	"github.com/go-webauthn/webauthn/protocol/webauthncose"
	"github.com/gravitational/trace"

	"github.com/gravitational/teleport"
	wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes"
	"github.com/gravitational/teleport/lib/darwin"
	logutils "github.com/gravitational/teleport/lib/utils/log"
)

var (
	ErrCredentialNotFound = errors.New("credential not found")
	ErrNotAvailable       = errors.New("touch ID not available")

	// PromptPlatformMessage is the message shown before Touch ID prompts.
	PromptPlatformMessage = "Using platform authenticator, follow the OS prompt"
	// PromptWriter is the writer used for prompt messages.
	PromptWriter io.Writer = os.Stderr

	logger = logutils.NewPackageLogger(teleport.ComponentKey, "TouchID")
)

func promptPlatform() {
	if PromptPlatformMessage != "" {
		fmt.Fprintln(PromptWriter, PromptPlatformMessage)
	}
}

// AuthContext is an optional, shared authentication context.
// Allows reusing a single authentication prompt/gesture between different

View on GitHub (pinned to 1283425b60)

Solutions

  1. Re-register the Touch ID credential (e.g. `tsh mfa add`) for the user.
  2. Check errors.Is(err, touchid.ErrCredentialNotFound) and fall back to another MFA device or prompt the user to enroll.
  3. Enumerate available credentials first (list credential infos) and handle the zero-length case explicitly rather than assuming a credential exists.

Example fix

// before
resp, err := touchid.Login(origin, req, user)
if err != nil {
    return trace.Wrap(err)
}
// after
resp, err := touchid.Login(origin, req, user)
if errors.Is(err, touchid.ErrCredentialNotFound) {
    return fallbackToOtherMFAMethod(ctx) // e.g. prompt for WebAuthn/OTP
}
if err != nil {
    return trace.Wrap(err)
}
Defensive patterns

Strategy: type-guard

Validate before calling

infos, err := touchid.ListCredentials()
if err != nil || len(infos) == 0 { return errors.New("no Touch ID credentials registered") }

Type guard

if errors.Is(err, touchid.ErrCredentialNotFound) { /* no matching Touch ID credential */ }

Try / catch

resp, err := touchid.Login(origin, req, user)
if errors.Is(err, touchid.ErrCredentialNotFound) {
    return promptOtherMFAMethod(ctx) // OTP/WebAuthn fallback
}

Prevention

When it happens

Trigger: Calling Login for a user with no registered Touch ID credential, pickCredential when no credential matches the RP/user, or DeleteCredential/DeleteNonInteractive/TestRegister_rollback targeting a nonexistent credential ID.

Common situations: Touch ID credentials deleted at the OS level (System Settings or keychain reset); switching macOS user accounts; running tsh mfa/tsh login before ever registering Touch ID; credential list wiped by macOS updates.

Related errors


AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02). Data as JSON: /api/errors/0423a0a0db89b801. Report an issue: GitHub.