amir20/dozzle · warning · ErrInvalidCredentials

invalid credentials

Error message

invalid credentials

What it means

ErrInvalidCredentials is the sentinel error returned by SimpleAuth.CreateToken in internal/auth/simple.go when the username cannot be found in the user database or the bcrypt-style password comparison fails. The HTTP layer maps it to a failed login attempt.

Solutions

  1. Double-check the username and password against ./data/users.yml (usernames are case-sensitive keys)
  2. Regenerate the password entry with a properly hashed value matching the expected hash format
  3. Verify users.yml parses as valid YAML with correct indentation for each user entry
  4. Confirm the auth provider is `simple` mode and the file path configured is the one actually loaded
Defensive patterns

Strategy: validation

Validate before calling

// before calling login
const users, _ := loadUsersYAML(path) // verify file parses and username key exists
if _, ok := users[username]; !ok {
    return fmt.Errorf("user %q not present in users.yml", username)
}

Try / catch

token, err := auth.CreateToken(username, password)
if errors.Is(err, auth.ErrInvalidCredentials) {
    http.Error(w, "invalid username or password", http.StatusUnauthorized)
    return
}

Prevention

When it happens

Trigger: CreateToken(username, password) is called (login endpoint) and find(username) misses, or CompareHashAndPassword(user.Password, password) returns false.

Common situations: users.yml doesn't exist or has no such user; the password is wrong; users.yml was edited but the entry was malformed/removed; client sends an email-style username while the file has a plain name (or vice versa); YAML indentation mistakes produce empty/partial user records.

Understand the failure class

Background: "User not found", "Invalid user", and "does not exist": what missing-user lookup errors mean across Rocket.Chat, LiteLLM, Phabricator, rustfs, and pnpm — this error's family across 10 libraries.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/6d9bd0467aaa8c78. Report an issue: GitHub.

Appendix: source

Thrown at internal/auth/simple.go:26

	"net/http"
	"slices"
	"sync"
	"time"

	"github.com/go-chi/jwtauth/v5"
	"github.com/rs/zerolog/log"
)

type simpleAuthContext struct {
	UserDatabase UserDatabase
	tokenAuth    *jwtauth.JWTAuth
	ttl          time.Duration
	// UserDatabase.Find reloads users.yml in place, and the middleware now calls it
	// on every request, so the reload has to be serialized.
	mu sync.Mutex
}

var ErrInvalidCredentials = errors.New("invalid credentials")

func NewSimpleAuth(userDatabase UserDatabase, ttl time.Duration) *simpleAuthContext {
	// Hash the users in a stable order. Ranging over the map directly makes the
	// digest depend on Go's randomized map iteration order, so any users.yml with
	// more than one user derives a different signing key on every start and
	// silently invalidates every session on restart.
	h := sha256.New()
	for _, username := range slices.Sorted(maps.Keys(userDatabase.Users)) {
		user := userDatabase.Users[username]
		h.Write([]byte(user.Password))
		h.Write([]byte(user.RolesConfigured))
	}

	tokenAuth := jwtauth.New("HS256", h.Sum(nil), nil)

	return &simpleAuthContext{
		UserDatabase: userDatabase,
		tokenAuth:    tokenAuth,

View on GitHub (pinned to d9463cbe21)