lcobucci/jwt · error · Lcobucci\JWT\Token\InvalidTokenStructure
headers must be an array with non-empty-string keys
Error message
headers must be an array with non-empty-string keys
What it means
Parser::parseHeader() Base64Url-decodes and JSON-decodes the header segment, then requires the result to be an array. If it decodes to a scalar/string/other type, InvalidTokenStructure::arrayExpected('headers') is thrown with this message. A JWT header must be a JSON object.
Solutions
- Verify the token is issued by a standards-compliant JWT library — header must be a JSON object like {"typ":"JWT","alg":"HS256"}
- Catch InvalidTokenStructure and reject the token as malformed
- Debug by base64url-decoding the first segment manually and inspecting the JSON
- Check your own encoder if you generate tokens — do not json_encode a list as the header
Example fix
// before
$token = $parser->parse($jwt); // header decodes to '["a","b"]'
// after
$decoded = SodiumBase64Polyfill::base64UrlDecode(explode('.', $jwt)[0]);
if (!str_starts_with(trim($decoded), '{')) {
throw new InvalidArgumentException('JWT header must be a JSON object');
}
$token = $parser->parse($jwt); Defensive patterns
Strategy: validation
Validate before calling
$h = SodiumBase64Polyfill::base64UrlDecode(explode('.', $jwt)[0]); if (!is_array(json_decode($h, true))) { throw new InvalidArgumentException('Header is not a JSON object'); } Try / catch
try { $token = $parser->parse($jwt); } catch (Lcobucci\JWT\InvalidTokenStructure $e) { return error_401('Malformed token header'); } Prevention
- Issue tokens only with standards-compliant JWT libraries
- Inspect decoded segments during debugging to find the corrupt part
- Catch InvalidTokenStructure at the auth boundary
When it happens
Trigger: The header segment decodes to a JSON array like '[1,2]' or a bare value like '42' or '"abc"' instead of an object; passing an arbitrary base64url string as the first segment.
Common situations: Hand-crafted or corrupted tokens; mixing up segment order (passing a payload that is a JSON array as the header); tokens produced by broken custom encoders.
Related errors
- The JWT string is missing the Header part
- The JWT string is missing the Claim part
- The JWT string is missing the Signature part
- The JWT string must have two dots
- claims must be an array with non-empty-string keys
AI-assisted analysis of lcobucci/jwt@375813049c (2026-09-14).
Data as JSON: /api/errors/d2cb30d43ff21628.
Report an issue: GitHub.
Appendix: source
Thrown at src/Token/Parser.php:86
return $data;
}
/**
* Parses the header from a string
*
* @param non-empty-string $data
*
* @return array<non-empty-string, mixed>
*
* @throws UnsupportedHeaderFound When an invalid header is informed.
* @throws InvalidTokenStructure When parsed content isn't an array.
*/
private function parseHeader(string $data): array
{
$header = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));
if (! is_array($header)) {
throw InvalidTokenStructure::arrayExpected('headers');
}
$this->guardAgainstEmptyStringKeys($header, 'headers');
if (array_key_exists('enc', $header)) {
throw UnsupportedHeaderFound::encryption();
}
if (! array_key_exists('typ', $header)) {
$header['typ'] = 'JWT';
}
return $header;
}
/**
* Parses the claim set from a string
*View on GitHub (pinned to 375813049c)