AlistGo/alist · error
token is invalidated
Error message
token is invalidated
What it means
Returned by common.ParseToken when IsTokenInvalidated reports the token as invalidated. Tokens are tracked in an in-memory cache: GenerateToken inserts them and InvalidateToken deletes them; a token absent from the cache counts as invalidated. Note this makes every token invalid after a restart or on a different instance, since the cache is not persisted.
Source
Thrown at server/common/auth.go:46
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(conf.Conf.TokenExpiresIn) * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
}}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)
tokenString, err = token.SignedString(SecretKey)
if err != nil {
return "", err
}
validTokenCache.Set(tokenString, true)
return tokenString, err
}
func ParseToken(tokenString string) (*UserClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
return SecretKey, nil
})
if IsTokenInvalidated(tokenString) {
return nil, errors.New("token is invalidated")
}
if err != nil {
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, errors.New("that's not even a token")
} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
return nil, errors.New("token is expired")
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
return nil, errors.New("token not active yet")
} else {
return nil, errors.New("couldn't handle this token")
}
}
}
if claims, ok := token.Claims.(*UserClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("couldn't handle this token")View on GitHub (pinned to 843d9dc814)
Solutions
- Have the client re-authenticate to obtain a fresh token when it receives this error
- Catch invalidation at the HTTP layer and return 401 so clients know to re-login
- In multi-instance deployments, front the instances with a shared session store or sticky sessions, since the cache is per-process memory
Example fix
// before
claims, err := common.ParseToken(token) // fails after restart
// after
claims, err := common.ParseToken(token)
if err != nil && strings.Contains(err.Error(), "invalidated") {
c.JSON(http.StatusUnauthorized, gin.H{"error": "token invalidated, please re-login"})
return
} Defensive patterns
Strategy: try-catch
Try / catch
claims, err := common.ParseToken(tok)
if err != nil && strings.Contains(err.Error(), "invalidated") {
c.AbortWithStatusJSON(401, gin.H{"error": "token invalidated; re-login required"})
return
} Prevention
- Clients should auto re-login on 401/invalidated
- Remember tokens do not survive server restarts (in-memory cache)
When it happens
Trigger: Parsing a JWT that was never generated by this process (cache miss), or one explicitly invalidated via InvalidateToken after logout/password change. Also fires for any token after an alist restart because validTokenCache starts empty.
Common situations: Client keeps using a token across an alist restart; horizontal deployment where the request hits an instance that did not issue the token; token invalidated server-side by a password change (PwdTS) or logout.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- that's not even a token
- token is expired
- token not active yet
- couldn't handle this token
- failed to refresh token: sub not match
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/dc17964831451979.
Report an issue: GitHub.