tymondesigns/jwt-auth · error · Tymon\JWTAuth\Exceptions\TokenInvalidException

Could not decode token: {exception message}

Error message

Could not decode token: {exception message}

What it means

Thrown by the Lcobucci provider's decode() when lcobucci/jwt's parser cannot parse the input string into a Plain token; the parser's message is appended and chained. It means the string had a token-like shape but its segments are not valid base64url-encoded JSON. Note the package's TokenValidator usually rejects shape problems first, so reaching this error means there were 3 dot-separated segments whose content could not be parsed.

Source

Thrown at src/Providers/JWT/Lcobucci.php:113

            throw new JWTException('Could not create token: '.$e->getMessage(), $e->getCode(), $e);
        }
    }

    /**
     * Decode a JSON Web Token.
     *
     * @param  string  $token
     * @return array
     *
     * @throws \Tymon\JWTAuth\Exceptions\JWTException
     */
    public function decode($token)
    {
        try {
            /** @var \Lcobucci\JWT\Token\Plain */
            $token = $this->config->parser()->parse($token);
        } catch (Exception $e) {
            throw new TokenInvalidException('Could not decode token: '.$e->getMessage(), $e->getCode(), $e);
        }

        if (! $this->config->validator()->validate($token, ...$this->config->validationConstraints())) {
            throw new TokenInvalidException('Token Signature could not be verified.');
        }

        return Collection::wrap($token->claims()->all())
            ->map(function ($claim) {
                if ($claim instanceof DateTimeInterface) {
                    return $claim->getTimestamp();
                }

                return is_object($claim) && method_exists($claim, 'getValue')
                    ? $claim->getValue()
                    : $claim;
            })
            ->toArray();
    }

View on GitHub (pinned to 6c70930a92)

Solutions

  1. Log the exact raw token received and compare it byte-for-byte with the token your issuer produced (often a transport/encoding mangling, not a JWT problem)
  2. Manually decode the segments to find the broken one: list($h, $p, $s) = explode('.', $token); json_decode(base64_decode(strtr($h, '-_', '+/'))); - whichever segment fails is the culprit
  3. Fix the client so the token is transmitted unchanged; if it came through a URL, make sure it is not urldecoded twice or re-encoded
  4. If the token is genuinely corrupt, have the client discard it and obtain a fresh one via login/refresh

Example fix

// before - sending a mangled token
$response = $client->withHeaders(['Authorization' => 'Bearer ' . urlencode($token)])->get($url);

// after - JWTs are URL-safe already; send unchanged
$response = $client->withHeaders(['Authorization' => 'Bearer ' . $token])->get($url);
Defensive patterns

Strategy: validation

Validate before calling

// Cheap pre-check before handing the string to decode()
function isParsableJwt(string $token): bool
{
    $parts = explode('.', $token);
    if (count($parts) !== 3) {
        return false;
    }
    foreach ([0, 1] as $i) {
        $json = json_decode((string) base64_decode(strtr($parts[$i], '-_', '+/'), true), true);
        if (!is_array($json)) {
            return false;
        }
    }
    return true;
}

Type guard

function isParsableJwt(string $token): bool
{
    $parts = explode('.', $token);
    if (count($parts) !== 3) {
        return false;
    }
    foreach ([0, 1] as $i) {
        $decoded = base64_decode(strtr($parts[$i], '-_', '+/'), true);
        if ($decoded === false || json_decode($decoded, true) === null) {
            return false;
        }
    }
    return true;
}

Try / catch

use Tymon\JWTAuth\Exceptions\TokenInvalidException;

try {
    $user = auth('api')->parseToken()->authenticate();
} catch (TokenInvalidException $e) {
    return response()->json(['error' => 'token_invalid'], 401);
}

Prevention

When it happens

Trigger: Calling JWTAuth::parseToken()->authenticate(), JWTAuth::decode(new Token($token)), or the middleware auth:api when the presented string has three segments but the header or payload is not valid base64url JSON, the signature segment contains non-base64url characters, or arbitrary junk like 'undefined.eyJ9.x' / an empty signature body is sent.

Common situations: Token mangled by URL encoding/decoding in transit (base64url - and _ converted), double-encoded base64, tokens copied with truncation or inserted line breaks, tokens produced by another library with a non-standard encoding, or a client concatenating strings into the Authorization header.

Related errors


AI-assisted analysis of tymondesigns/jwt-auth@6c70930a92 (2026-08-21). Data as JSON: /api/errors/2546e3c97f818f6e. Report an issue: GitHub.