{"record":{"id":"1b6bc2b9141975fd","repo":"juanfont/headscale","slug":"failed-to-parse-auth-key","errorCode":null,"errorMessage":"failed to parse auth-key","messagePattern":"failed to parse auth-key","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/db/preauth_keys.go","lineNumber":183,"sourceCode":"\t}\n\n\treturn keys, nil\n}\n\n// ListPreAuthKeysByUser returns all [types.PreAuthKey] values belonging to a specific user.\nfunc ListPreAuthKeysByUser(tx *gorm.DB, uid types.UserID) ([]types.PreAuthKey, error) {\n\tvar keys []types.PreAuthKey\n\n\terr := tx.Preload(\"User\").Where(\"user_id = ?\", uint(uid)).Find(&keys).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn keys, nil\n}\n\nvar (\n\tErrPreAuthKeyFailedToParse    = errors.New(\"failed to parse auth-key\")\n\tErrPreAuthKeyNotTaggedOrOwned = errors.New(\"auth-key must be either tagged or owned by user\")\n)\n\nfunc findAuthKey(tx *gorm.DB, keyStr string) (*types.PreAuthKey, error) {\n\tvar pak types.PreAuthKey\n\n\t// Validate input is not empty\n\tif keyStr == \"\" {\n\t\treturn nil, ErrPreAuthKeyFailedToParse\n\t}\n\n\t_, prefixAndHash, found := strings.Cut(keyStr, authKeyPrefix)\n\n\tif !found {\n\t\t// Legacy format (plaintext) - backwards compatibility\n\t\terr := tx.Preload(\"User\").First(&pak, \"key = ?\", keyStr).Error\n\t\tif err != nil {\n\t\t\treturn nil, ErrPreAuthKeyNotFound","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/preauth_keys.go#L165-L201","documentation":"Sentinel in hscontrol/db/preauth_keys.go returned by findAuthKey (preauth_keys.go:183+) when the presented auth-key string is structurally invalid: empty, missing the authKeyPrefix marker, or with prefix/secret segments of the wrong shape. It fires before any database lookup, distinguishing format errors from 'key not found'. A sibling error ErrPreAuthKeyNotTaggedOrOwned covers ownership validation.","triggerScenarios":"Calling the registration path with an empty key, a truncated key, a key without the expected prefix (strings.Cut on authKeyPrefix fails), or a non-auth-key credential pasted into --auth-key.","commonSituations":"Scripts interpolating the key from an unset environment variable (empty string); keys copied with missing characters; passing an API key (hskey-api-...) as an auth-key.","solutions":["Re-copy the full authkey from `headscale preauthkeys create` output — it must be one unbroken string","Check the variable holding the key is set and unmodified in your provisioning script (print its length, not the value)","Confirm you are using an auth-key, not an admin API key or oauth token"],"exampleFix":"# before\nkey=\"$AUTH_KEY\"   # AUTH_KEY unset -> empty string\ntailscale up --auth-key=\"$key\"\n\n# after\n: \"${AUTH_KEY:?AUTH_KEY must be set}\"\ntailscale up --auth-key=\"$AUTH_KEY\"","handlingStrategy":"type-guard","validationCode":"// fail fast in provisioning scripts before calling tailscale\nif [ -z \"$AUTH_KEY\" ]; then\n    echo \"AUTH_KEY is empty\" >&2; exit 1\nfi\ncase \"$AUTH_KEY\" in\n    authkey-*) ;;\n    *) echo \"not an auth-key\" >&2; exit 1 ;;\nesac","typeGuard":"func isPreAuthKey(s string) bool {\n    if s == \"\" {\n        return false\n    }\n    // must contain the auth-key prefix marker with non-empty remainder\n    _, rest, found := strings.Cut(s, authKeyPrefix)\n    return found && len(rest) > 0\n}","tryCatchPattern":"pak, err := db.FindPreAuthKey(keyStr)\nif err != nil {\n    if errors.Is(err, db.ErrPreAuthKeyFailedToParse) {\n        return registrationFailed(401, \"malformed auth-key\") // do not retry the same string\n    }\n    return err\n}","preventionTips":["Assert key variables are non-empty (:- or :? shell expansions) before use","Never paste other credential types (hskey-api-, hskey-oauthtok-) into --auth-key","Keep keys whole in secret storage; avoid string surgery on credentials"],"tags":["preauth-key","validation","registration","headscale","go"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}