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
- Configure the token issuer to include a standard numeric exp claim (seconds since epoch).
- Use an ID token from a compliant issuer (Google, Auth0, etc.) rather than a custom/refresh token.
- 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
- Configure the issuer to always set exp.
- Validate the exp claim presence at token ingestion.
- Use ID tokens (which require exp) rather than refresh tokens.
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
- credentials: audience cannot be empty
- token file
- 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/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)