grpc/grpc-go · error

token file

Error message

token file %q: %v: %w

What it means

Returned by jwtFileReader.readToken wrapping any failure from extractExpiration (the %v is the inner error: bad format, decode failure, missing claims, or expiry). The sentinel errJWTValidation maps to codes.Unauthenticated at the call site. This is the generic wrapper for a structurally invalid or expired JWT in the file.

Solutions

  1. Open the token file and decode the payload (jwt.io, base64 -d) to inspect the claims and exp.
  2. Ensure the token writer emits a complete JWT atomically (write to a temp file then rename).
  3. Sync the system clock (ntp/chrony) if expiry looks wrong.
  4. Regenerate the token and verify it parses with the same library before deploying.
Defensive patterns

Strategy: validation

Validate before calling

// Pre-parse the token with the same logic to give a precise startup error.
func validateTokenFile(path string) error {
    b, err := os.ReadFile(path)
    if err != nil {
        return err
    }
    tok := strings.TrimSpace(string(b))
    parts := strings.Split(tok, ".")
    if len(parts) != 3 {
        return fmt.Errorf("token in %q is not a 3-part JWT", path)
    }
    payload, err := base64.RawURLEncoding.DecodeString(parts[1])
    if err != nil {
        return fmt.Errorf("token payload decode: %w", err)
    }
    var c struct{ Exp int64 `json:"exp"` }
    if err := json.Unmarshal(payload, &c); err != nil {
        return fmt.Errorf("token payload json: %w", err)
    }
    if c.Exp == 0 {
        return fmt.Errorf("token has no exp claim")
    }
    if time.Unix(c.Exp, 0).Before(time.Now()) {
        return fmt.Errorf("token expired")
    }
    return nil
}

Prevention

When it happens

Trigger: The file contains a string that is not a well-formed JWT (no exp claim, malformed base64, expired token); the file was overwritten mid-rotation with a partial token; the wrong kind of token (opaque instead of JWT) was written.

Common situations: Token rotation leaving a truncated file; using an opaque OAuth access token where a JWT was expected; clock skew making a valid token appear expired; misconfigured token broker.

Related errors


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

Appendix: source

Thrown at credentials/jwt/file_reader.go:62

	tokenFilePath string
}

// readToken reads and parses a JWT token from the configured file.
// Returns the token string, expiration time, and any error encountered.
func (r *jwtFileReader) readToken() (string, time.Time, error) {
	tokenBytes, err := os.ReadFile(r.tokenFilePath)
	if err != nil {
		return "", time.Time{}, fmt.Errorf("%v: %w", err, errTokenFileAccess)
	}

	token := strings.TrimSpace(string(tokenBytes))
	if token == "" {
		return "", time.Time{}, fmt.Errorf("token file %q is empty: %w", r.tokenFilePath, errJWTValidation)
	}

	exp, err := r.extractExpiration(token)
	if err != nil {
		return "", time.Time{}, fmt.Errorf("token file %q: %v: %w", r.tokenFilePath, err, errJWTValidation)
	}

	return token, exp, nil
}

const tokenDelim = "."

// extractClaimsRaw returns the JWT's claims part as raw string. Even though the
// header and signature are not used, it still expects that the input string to
// be well-formed (ie comprised of exactly three parts, separated by a dot
// character).
func extractClaimsRaw(s string) (string, bool) {
	_, s, ok := strings.Cut(s, tokenDelim)
	if !ok { // no period found
		return "", false
	}
	claims, s, ok := strings.Cut(s, tokenDelim)
	if !ok { // only one period found

View on GitHub (pinned to 0c51461d27)