Tencent/WeKnora · error

token not yet valid

Error message

token not yet valid

What it means

verifyExternalUserJWT validates an externally-issued user JWT used for API principal resolution. After checking expiry and max lifetime, it rejects any token whose 'nbf' (not-before) claim is in the future. This is thrown because the token is cryptographically valid but is being used before its official start time.

Source

Thrown at internal/middleware/auth.go:646

			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return []byte(secret), nil
	})
	if err != nil {
		return "", err
	}
	if token == nil || !token.Valid {
		return "", errors.New("invalid external user token")
	}
	exp, err := claims.GetExpirationTime()
	if err != nil || exp == nil {
		return "", errors.New("missing expiration")
	}
	if time.Until(exp.Time) > maxExternalUserTokenTTL {
		return "", fmt.Errorf("token lifetime exceeds %s", maxExternalUserTokenTTL)
	}
	if nbf, nbfErr := claims.GetNotBefore(); nbfErr == nil && nbf != nil && time.Now().Before(nbf.Time) {
		return "", errors.New("token not yet valid")
	}
	if got := principalTenantIDFromClaims(claims); got != tenantID {
		return "", fmt.Errorf("workspace mismatch: got %d want %d", got, tenantID)
	}
	sub, _ := claims["sub"].(string)
	sub = strings.TrimSpace(sub)
	if sub == "" {
		return "", errors.New("missing subject")
	}
	return sub, nil
}

func validateExternalUserID(id string) error {
	id = strings.TrimSpace(id)
	if id == "" {
		return errors.New("empty external user id")
	}
	if len(id) > maxExternalUserIDLen {

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Synchronize clocks: run NTP/chrony on both the token issuer and the API server so skew is well under the token's leeway.
  2. Check how the external token is minted: set the nbf claim to now (or omit it) instead of a future timestamp.
  3. Retry after waiting until nbf passes, if the token is intentionally future-dated.
  4. Compare the raw token's nbf value against server time (decode the JWT payload) to confirm the skew amount before issuing new tokens.

Example fix

// before: issuer minted token with future nbf
token := issueJWT(sub, tenantID, jwt.MapClaims{"nbf": time.Now().Add(5 * time.Minute).Unix()})
// after: token valid immediately
token := issueJWT(sub, tenantID, jwt.MapClaims{"nbf": time.Now().Add(-30 * time.Second).Unix()})
Defensive patterns

Strategy: validation

Validate before calling

// decode payload without verification, check nbf before calling the API
parts := strings.Split(token, ".")
payload, _ := base64.RawURLEncoding.DecodeString(parts[1])
var c map[string]any
json.Unmarshal(payload, &c)
if nbf, ok := c["nbf"].(float64); ok && time.Now().Before(time.Unix(int64(nbf), 0)) {
    return fmt.Errorf("token not valid until %v, check clock sync", time.Unix(int64(nbf), 0))
}

Type guard

func tokenIsActive(claims jwt.MapClaims) bool {
    if nbf, err := claims.GetNotBefore(); err != nil || nbf == nil {
        return err == nil
    } else {
        return !time.Now().Before(nbf.Time)
    }
}

Prevention

When it happens

Trigger: Calling resolveAPIPrincipal with an external user JWT whose nbf claim is later than the server's current clock. Typically caused by clock skew between token issuer and this server, or by minting a token with a not-before time set too far ahead.

Common situations: Distributed deployments where the token-issuing service's clock is ahead of the API server's clock (NTP drift); tokens generated for scheduled activation (future-dated tokens) used immediately; local dev environments without synchronized clocks.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/82f5bbc0dd23b5b7. Report an issue: GitHub.