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

  1. Have the client re-authenticate to obtain a fresh token when it receives this error
  2. Catch invalidation at the HTTP layer and return 401 so clients know to re-login
  3. 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

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

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/dc17964831451979. Report an issue: GitHub.