grpc/grpc-go · error

no expiration claims

Error message

no expiration claims

What it means

Returned by extractExpiration when the decoded JSON claims have exp == 0 (the field is absent or explicitly zero). Without an expiration the reader cannot decide freshness, so it rejects the token. The sentinel errJWTValidation maps to codes.Unauthenticated.

Solutions

  1. Configure the token issuer to include a standard numeric exp claim (seconds since epoch).
  2. Use an ID token from a compliant issuer (Google, Auth0, etc.) rather than a custom/refresh token.
  3. Inspect the decoded payload to confirm the exp field name and type.
Defensive patterns

Strategy: type-guard

Validate before calling

func hasExpClaim(claimsSeg string) bool {
    b, _ := base64.RawURLEncoding.DecodeString(claimsSeg)
    var c struct{ Exp int64 `json:"exp"` }
    _ = json.Unmarshal(b, &c)
    return c.Exp != 0
}

Type guard

func hasExpiry(claims map[string]any) bool {
    exp, ok := claims["exp"]
    if !ok {
        return false
    }
    switch v := exp.(type) {
    case float64:
        return v != 0
    case int64:
        return v != 0
    case json.Number:
        n, err := v.Int64()
        return err == nil && n != 0
    }
    return false
}

Prevention

When it happens

Trigger: A JWT issued without an exp claim (non-compliant with the reader's requirement); a token with a differently-named/typed expiry field; a refresh token or assertion that omits exp.

Common situations: Custom token issuer that does not set exp; using an OAuth refresh token instead of an ID token; field name mismatch (e.g. expiry vs exp).

Related errors


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

Appendix: source

Thrown at credentials/jwt/file_reader.go:107

// extractExpiration parses the JWT token to extract the expiration time.
func (r *jwtFileReader) extractExpiration(token string) (time.Time, error) {
	claimsRaw, ok := extractClaimsRaw(token)
	if !ok {
		return time.Time{}, fmt.Errorf("expected 3 parts in token")
	}
	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)