lcobucci/jwt · error · Lcobucci\JWT\Encoding\CannotDecodeContent

Error while decoding from JSON

Error message

Error while decoding from JSON

What it means

JoseEncoder::jsonDecode wraps json_decode() with JSON_THROW_ON_ERROR; when PHP's json_decode() raises a JsonException (malformed JSON, invalid UTF-8, depth overflow), the encoder rethrows it wrapped in CannotDecodeContent::jsonIssues with the generic message 'Error while decoding from JSON'. It is thrown whenever the library must decode a JSON payload (e.g. a JWT's header or claims segment) and the input is not valid JSON.

Solutions

  1. Inspect the raw string being passed to jsonDecode() (var_dump/log it) and validate it with json_decode($json) manually to see the exact JsonException message.
  2. Fix the producer of the JSON: ensure the client sends valid JSON (Content-Type: application/json, no HTML error pages in the body).
  3. For JWT workflows, verify the token is three dot-separated base64url segments and base64url-decode each segment before JSON decoding.
  4. Catch CannotDecodeContent at the boundary and return a 400-style response instead of leaking the exception.
  5. Check that the data wasn't mangled by transport (trailing whitespace is fine, but NUL bytes or charset conversion damage is not).

Example fix

// before
$claims = $encoder->jsonDecode($request->getBody()); // throws CannotDecodeContent on bad JSON

// after
$raw = $request->getBody();
if (json_validate($raw)) {
    $claims = $encoder->jsonDecode($raw);
} else {
    throw new BadRequestException('Request body is not valid JSON');
}
Defensive patterns

Strategy: try-catch

Validate before calling

// PHP 8.3+
if (!function_exists('json_validate') ? json_decode($raw) === null && json_last_error() !== JSON_ERROR_NONE : !json_validate($raw)) {
    throw new InvalidArgumentException('Input is not valid JSON');
}

Type guard

function isJsonString(?string $raw): bool
{
    if ($raw === null || $raw === '') {
        return false;
    }
    json_decode($raw);
    return json_last_error() === JSON_ERROR_NONE;
}

Try / catch

try {
    $data = $encoder->jsonDecode($raw);
} catch (CannotDecodeContent $e) {
    // $e->getPrevious() is the JsonException with the exact offset/reason
    error_log('JSON decode failed: ' . $e->getPrevious()?->getMessage());
    throw new BadRequestException('Malformed JSON input', previous: $e);
}

Prevention

When it happens

Trigger: Calling jsonDecode() (directly or via token parsing) with a string that is not valid JSON: truncated JWT segments, base64url strings decoded to garbage, double-encoded JSON, or invalid UTF-8 bytes.

Common situations: Receiving a token that was truncated or corrupted in transport/cookies; decoding a payload segment without first base64url-decoding it; a client sending raw form-encoded or XML bodies to an endpoint that expects JSON; PHP json_decode failing on invalid UTF-8 from external systems.

Related errors


AI-assisted analysis of lcobucci/jwt@375813049c (2026-09-14). Data as JSON: /api/errors/6617c20bb225d923. Report an issue: GitHub.

Appendix: source

Thrown at src/Encoding/JoseEncoder.php:37

 * A utilitarian class that encodes and decodes data according to JOSE specifications
 */
final readonly class JoseEncoder implements Encoder, Decoder
{
    public function jsonEncode(mixed $data): string
    {
        try {
            return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
        } catch (JsonException $exception) {
            throw CannotEncodeContent::jsonIssues($exception);
        }
    }

    public function jsonDecode(string $json): mixed
    {
        try {
            return json_decode(json: $json, associative: true, flags: JSON_THROW_ON_ERROR);
        } catch (JsonException $exception) {
            throw CannotDecodeContent::jsonIssues($exception);
        }
    }

    public function base64UrlEncode(string $data): string
    {
        return SodiumBase64Polyfill::bin2base64(
            $data,
            SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,
        );
    }

    public function base64UrlDecode(string $data): string
    {
        return SodiumBase64Polyfill::base642bin(
            $data,
            SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,
        );
    }

View on GitHub (pinned to 375813049c)