gravitational/teleport · error

no credentials for user %q

Error message

no credentials for user %q

What it means

pickAssertion in the FIDO2 login flow selects which returned assertion to use. When a target username was supplied (user != "") and none of the assertions returned by the security key carry a matching assertion.User.Name, the flow fails with this error rather than guessing. It means the key authenticated but has no credential bound to that specific user account.

Source

Thrown at lib/auth/webauthncli/fido2.go:420

	case l == 0:
		return nil, errors.New("authenticator returned empty assertions")

	// MFA or single account.
	// Note that authenticators don't return the user name, display name or icon
	// for a single account per RP.
	// See the authenticatorGetAssertion response, user member (0x04):
	// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorgetassertion-response-structure
	case !passwordless, l == 1:
		return assertions[0], nil

	// Explicit user required. First occurrence wins.
	case user != "":
		for _, assertion := range assertions {
			if assertion.User.Name == user {
				return assertion, nil
			}
		}
		return nil, fmt.Errorf("no credentials for user %q", user)
	}

	// Prepare credentials and show picker.
	creds := make([]*CredentialInfo, len(assertions))
	credToAssertion := make(map[*CredentialInfo]*libfido2.Assertion)
	for i, assertion := range assertions {
		cred := &CredentialInfo{
			ID: assertion.CredentialID,
			User: UserInfo{
				UserHandle: assertion.User.ID,
				Name:       assertion.User.Name,
			},
		}
		credToAssertion[cred] = assertion
		creds[i] = cred
	}
	chosen, err := prompt.PromptCredential(creds)
	if err != nil {

View on GitHub (pinned to 1283425b60)

Solutions

  1. Verify the username passed to the login flow exactly matches the one used at registration (case-sensitive).
  2. List credentials on the device (or let the user pick without a name filter) to see which users the key actually holds.
  3. Register the user on the current security key with fido2Register before attempting login.
  4. If the credential was registered with an empty username, call login with an empty user to trigger the credential picker instead of name matching.

Example fix

// before
assertion, err := fido2Login(ctx, cfg, user="Alice", ...) // key has "alice"
// after — match exactly or fall back to picker
if !hasAssertionForUser(assertions, user) {
    user = "" // empty user => interactive credential picker
}
assertion, err := fido2Login(ctx, cfg, user, ...)
Defensive patterns

Strategy: validation

Validate before calling

creds, err := fido2.ListCredentials(...)
if err != nil { return err }
hasUser := false
for _, c := range creds {
    if c.User == user { hasUser = true; break }
}
if !hasUser {
    return fmt.Errorf("user %q is not registered on this security key", user)
}

Type guard

func keyHasUser(assertions []*libfido2.Assertion, user string) bool {
    for _, a := range assertions {
        if a.User != nil && a.User.Name == user { return true }
    }
    return false
}

Try / catch

assertion, err := pickAssertion(ctx, cfg, assertions, user, prompt)
if err != nil {
    if strings.Contains(err.Error(), "no credentials for user") {
        // retry without name filter so the interactive picker runs
        return pickAssertion(ctx, cfg, assertions, "", prompt)
    }
    return trace.Wrap(err)
}

Prevention

When it happens

Trigger: Calling login with a specific username against a FIDO2 device that holds credentials for other users (or for which resident credentials for that user do not exist), so all returned assertions have User.Name != user.

Common situations: Typos or different casing in the username; the user's credential was registered on a different security key; the key was registered with an empty/different username (e.g. registered non-discoverable or under another account); after re-imaging or resetting a key that lost resident credentials.

Related errors


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