grpc/grpc-go · error

expired token

Error message

expired token

What it means

Returned by extractExpiration when the token's exp time is in the past relative to the process clock. The token was structurally valid but has already expired, so the reader refuses to use it. The sentinel errJWTValidation maps to codes.Unauthenticated.

Solutions

  1. Trigger a token refresh/redeployment so the file holds a current token.
  2. Verify the token-injection sidecar/init is healthy and writing fresh tokens before exp.
  3. Sync the system clock (ntp/chrony) if the expiry looks premature.
  4. Inspect exp vs current time: date -d @$(printf '%s' "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .exp).
Defensive patterns

Strategy: validation

Validate before calling

func tokenAlive(path string, skew time.Duration) error {
    b, err := os.ReadFile(path)
    if err != nil {
        return err
    }
    parts := strings.Split(strings.TrimSpace(string(b)), ".")
    if len(parts) != 3 {
        return errors.New("not a JWT")
    }
    payload, _ := base64.RawURLEncoding.DecodeString(parts[1])
    var c struct{ Exp int64 `json:"exp"` }
    _ = json.Unmarshal(payload, &c)
    if time.Unix(c.Exp, 0).Before(time.Now().Add(skew)) {
        return errors.New("token expired or about to expire")
    }
    return nil
}

Try / catch

// On RPC, codes.Unauthenticated with 'expired token' => refresh/redeploy:
if status.Code(err) == codes.Unauthenticated && strings.Contains(err.Error(), "expired token") {
    triggerTokenRefresh()
}

Prevention

When it happens

Trigger: The token file holds a stale token that has passed its exp; the token injector has stopped refreshing; the system clock is skewed forward; very short-lived tokens read after their window.

Common situations: Kubernetes projected token not being rotated (node issue); sidecar token agent crashed; NTP drift; deploying a token baked at build time that has since expired.

Understand the failure class

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/94e06a111b5aef82. Report an issue: GitHub.

Appendix: source

Thrown at credentials/jwt/file_reader.go:114

	payloadBytes, err := base64.RawURLEncoding.DecodeString(claimsRaw)
	if err != nil {
		return time.Time{}, fmt.Errorf("decode error: %v", err)
	}

	var claims jwtClaims
	if err := json.Unmarshal(payloadBytes, &claims); err != nil {
		return time.Time{}, fmt.Errorf("unmarshal error: %v", err)
	}

	if claims.Exp == 0 {
		return time.Time{}, fmt.Errorf("no expiration claims")
	}

	expTime := time.Unix(claims.Exp, 0)

	// Check if token is already expired.
	if expTime.Before(time.Now()) {
		return time.Time{}, fmt.Errorf("expired token")
	}

	return expTime, nil
}

View on GitHub (pinned to 0c51461d27)