dgraph-io/dgraph · error

Token is expired

Error message

Token is expired

What it means

errTokenExpired is a sentinel error returned by ParseJWT when the JWT library rejects a token because its exp claim is in the past. It signals the credential is structurally valid but no longer usable, so the caller must obtain a fresh token and retry rather than treating it as a configuration error.

Source

Thrown at x/jwt_helper.go:17

/*
 * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
 * SPDX-License-Identifier: Apache-2.0
 */

package x

import (
	"context"
	"fmt"

	"github.com/golang-jwt/jwt/v5"
	"github.com/pkg/errors"
)

var (
	errTokenExpired = errors.New("Token is expired")
)

// MaybeKeyToBytes converts the x.Sensitive type into []byte if the type of interface really
// is x.Sensitive. We keep the type x.Sensitive for private and public keys so that it
// doesn't get printed into the logs but the type the JWT library needs is []byte.
func MaybeKeyToBytes(k interface{}) interface{} {
	if kb, ok := k.(Sensitive); ok {
		return []byte(kb)
	}
	return k
}

func ParseJWT(jwtStr string) (jwt.MapClaims, error) {
	token, err := jwt.Parse(jwtStr, func(token *jwt.Token) (interface{}, error) {
		if WorkerConfig.AclJwtAlg == nil {
			return nil, errors.Errorf("ACL is disabled")
		}
		if token.Method.Alg() != WorkerConfig.AclJwtAlg.Alg() {

View on GitHub (pinned to 759e242be6)

Solutions

  1. Refresh the access JWT: re-login against /login (or the ACL refresh token flow) and retry the request with the new token
  2. Implement automatic retry-on-expired using the refresh token before re-authenticating fully
  3. Sync server clocks (NTP) if tokens appear expired while still fresh
  4. Increase token TTL if tokens legitimately expire too quickly for your workload

Example fix

// before
tok, err := x.ParseJWT(jwtStr, key) // err == errTokenExpired
// after
tok, err := x.ParseJWT(jwtStr, key)
if errors.Is(err, x.errTokenExpired) {
    jwtStr = refreshAccessJWT(refreshToken) // re-login / refresh token flow
    tok, err = x.ParseJWT(jwtStr, key)
}
Defensive patterns

Strategy: try-catch

Validate before calling

claims := jwt.MapClaims{}
if _, err := jwt.ParseWithClaims(jwtStr, claims, keyFunc); err == nil {
    if exp, ok := claims["exp"].(float64); ok && time.Now().After(time.Unix(int64(exp), 0)) {
        jwtStr = refreshAccessToken(refreshToken) // refresh before use
    }
}

Type guard

func tokenExpired(jwtStr string, key []byte) bool {
    _, err := x.ParseJWT(jwtStr, key)
    return err != nil && strings.Contains(err.Error(), "expired")
}

Try / catch

tok, err := x.ParseJWT(jwtStr, key)
if err != nil && errors.Is(err, x.errTokenExpired) {
    jwtStr = refreshOrRelogin(refreshToken)
    tok, err = x.ParseJWT(jwtStr, key) // retry once with fresh token
}

Prevention

When it happens

Trigger: Calling ParseJWT with a JWT whose exp timestamp has passed; long-lived processes caching an access token and using it after expiry; clock skew between issuer and verifier making a token appear expired.

Common situations: Dgraph ACL access JWTs used past their validity window; clients that refreshed credentials only at process start; server clocks out of sync (NTP drift); tokens minted with very short TTLs.

Understand the failure class

Related errors


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/b83e31f27d0a0067. Report an issue: GitHub.