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
- Open the token file and decode the payload (jwt.io, base64 -d) to inspect the claims and exp.
- Ensure the token writer emits a complete JWT atomically (write to a temp file then rename).
- Sync the system clock (ntp/chrony) if expiry looks wrong.
- 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
- Validate the token with a local parser at startup to surface the precise sub-error.
- Ensure the token writer writes atomically (temp file + rename).
- Sync the system clock to avoid false expiry.
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
- credentials: audience cannot be empty
- no expiration claims
- cannot send secure credentials on an insecure connection
- credentials: ctx cannot be nil
- decode error
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 foundView on GitHub (pinned to 0c51461d27)