gotify/server · error · errInvalidToken
invalid token
Error message
invalid token
What it means
errInvalidToken is the sentinel error (errors.Is-able) returned by ParseEnhancedToken when an enhanced token ('gtfy'-prefixed, signed) fails cryptographic verification — e.g. the ed25519 signature over the timestamp/payload does not verify against the server's public key. TestNewComplexToken also surfaces it when a crafted token fails verification.
Source
Thrown at auth/token.go:22
"crypto"
"crypto/ed25519"
"crypto/rand"
"crypto/sha512"
"encoding/base64"
"errors"
"fmt"
"math/big"
"strconv"
"strings"
)
const (
maxTimestampDiffSeconds = 15 * 60
randomTokenLength = 22 // ~2^132 keyspace
)
var (
errInvalidToken = errors.New("invalid token")
errCannotParseToken = errors.New("cannot parse token")
errNoPrivateKey = errors.New("no private key")
tokenCharacters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_")
pluginPrefix = "P"
enhancedTokenPrefix = "gtfy"
randReader = rand.Reader
)
type EnhancedToken struct {
ident string
pubOrPrivKey []byte
timestamp int64
signature []byte
}
// PublicForm returns the a canonicalized representation of the public key.
func (c *EnhancedToken) PublicForm() string {View on GitHub (pinned to 14bfc25627)
Solutions
- Request a fresh token from the server (login / token generation) and use it verbatim — no truncation or reformatting
- Ensure the token was issued by the same server/key you are validating against; do not share tokens across instances
- Check client clock skew: keep it well under 15 minutes or sync with NTP
- If you hand-build tokens, re-sign them with the correct ed25519 private key and the SHA-512 options the server uses
Example fix
// before: reconstructed token string
token := 'gtfy' + payloadPart + guessedSig
user, err := auth.ParseEnhancedToken(token)
// after: use the token exactly as issued by the server
token := os.Getenv("GITEA_TOKEN")
user, err := auth.ParseEnhancedToken(token)
// handle with errors.Is(err, auth.ErrInvalidToken) Defensive patterns
Strategy: try-catch
Validate before calling
// structural + provenance sanity checks before parsing
if (!token || !token.startsWith('gtfy')) throw new Error('not an enhanced token');
if (token !== tokenIssuedByThisServer) throw new Error('token from a different server/key'); Type guard
function isEnhancedToken(t) {
return typeof t === 'string' && t.startsWith('gtfy') && t.length > 4;
} Try / catch
user, err := ParseEnhancedToken(token)
if err != nil {
if errors.Is(err, errInvalidToken) {
// signature verification failed: token tampered, wrong key, or stale
return nil, fmt.Errorf("token rejected; request a fresh token from the server")
}
return nil, err
} Prevention
- Store and transmit tokens verbatim — no trimming, reformatting, or truncation
- Do not share tokens across server instances (different signing keys)
- Keep clocks synced (NTP); tokens are time-windowed (15 min)
- Regenerate the token after any key rotation or suspected compromise
When it happens
Trigger: Parsing a token whose signature was mutated/truncated (see token_test.go:44), a token signed by a different key than the server expects, a token replayed outside maxTimestampDiffSeconds (15 min) or with a corrupted timestamp, or a token whose payload was tampered with.
Common situations: Copying tokens between environments (staging token on production server); truncating the token in a shell variable or config; manually editing a token; clock skew beyond 15 minutes between issuance and validation after token regen.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- %w: private key must be %d bytes
- %w: public key must be %d bytes
- cannot parse token
- local authentication is disabled
- basic auth required
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/6171c125da333ca1.
Report an issue: GitHub.