grpc/grpc-go · error

expected 3 parts in token

Error message

expected 3 parts in token

What it means

extractClaimsRaw returns ok=false when the token does not have exactly two '.' delimiters producing three non-empty segments (file_reader.go:74-95). extractExpiration then returns 'expected 3 parts in token'. The reader requires a well-formed JWS compact serialization even though it only reads the payload.

Source

Thrown at credentials/jwt/file_reader.go:94

	if !ok { // no period found
		return "", false
	}
	claims, s, ok := strings.Cut(s, tokenDelim)
	if !ok { // only one period found
		return "", false
	}
	_, _, ok = strings.Cut(s, tokenDelim)
	if ok { // three periods found
		return "", false
	}
	return claims, true
}

// 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.

View on GitHub (pinned to 03255a9237)

Solutions

  1. Confirm the file content is a single-line compact JWT: header.payload.signature.
  2. Strip any internal whitespace/newlines before writing, not just leading/trailing.
  3. Point the reader at the actual ID token file, not an OAuth/refresh token or key file.
  4. Decode the string with a JWT tool to confirm the three-part structure.

Example fix

// before: stored a multi-line PEM-ish blob
// -----BEGIN TOKEN-----
// ey...
// -----END TOKEN-----

// after: store the compact serialization on one line
eyJhbGciOi...<payload>...<sig>
Defensive patterns

Strategy: validation

Validate before calling

// Check the three-part structure before use.
parts := strings.Split(strings.TrimSpace(tok), ".")
if len(parts) != 3 {
    return fmt.Errorf("token is not a 3-part JWT")
}

Try / catch

_, _, err := r.readToken()
if err != nil && strings.Contains(err.Error(), "expected 3 parts in token") {
    // token is not a compact JWT; replace the file contents.
    return err
}

Prevention

When it happens

Trigger: The token string lacks dots, has only one dot, or has more than two dots. Caused by a non-JWT string, a JWT split across lines, trailing/leading whitespace inside the segments, or a token that was URL-encoded.

Common situations: Wrong file (raw key, refresh token, base64 blob), a token that was base64-encoded as a whole before being written, or whitespace/newlines in the middle of the file that TrimSpace at line 55 does not remove (it only trims ends).

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/316eecff58d0fd39. Report an issue: GitHub.