{"record":{"id":"4c6e54f2a6b545ce","repo":"lcobucci/jwt","slug":"claims-must-be-an-array-with-non-empty-string-keys","errorCode":null,"errorMessage":"claims must be an array with non-empty-string keys","messagePattern":"claims must be an array with non-empty-string keys","errorType":"exception","errorClass":"Lcobucci\\JWT\\Token\\InvalidTokenStructure","httpStatus":null,"severity":"error","filePath":"src/Token/Parser.php","lineNumber":116,"sourceCode":"\n        return $header;\n    }\n\n    /**\n     * Parses the claim set from a string\n     *\n     * @param non-empty-string $data\n     *\n     * @return array<non-empty-string, mixed>\n     *\n     * @throws InvalidTokenStructure When parsed content isn't an array or contains non-parseable dates.\n     */\n    private function parseClaims(string $data): array\n    {\n        $claims = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));\n\n        if (! is_array($claims)) {\n            throw InvalidTokenStructure::arrayExpected('claims');\n        }\n\n        $this->guardAgainstEmptyStringKeys($claims, 'claims');\n\n        if (array_key_exists(RegisteredClaims::AUDIENCE, $claims)) {\n            $claims[RegisteredClaims::AUDIENCE] = (array) $claims[RegisteredClaims::AUDIENCE];\n        }\n\n        foreach (RegisteredClaims::DATE_CLAIMS as $claim) {\n            if (! array_key_exists($claim, $claims)) {\n                continue;\n            }\n\n            $claims[$claim] = $this->convertDate($claims[$claim]);\n        }\n\n        return $claims;\n    }","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/lcobucci/jwt/blob/375813049c24c7111bda8b6884c57b071ceb2fe7/src/Token/Parser.php#L98-L134","documentation":"Parser::parseClaims() Base64Url-decodes and JSON-decodes the claims segment and requires the result to be an array (JSON object). If it decodes to a scalar or another non-array type, InvalidTokenStructure::arrayExpected('claims') is thrown. JWT payload must be a JSON object of claims.","triggerScenarios":"The payload segment decodes to 'null', a number, a string, or a JSON array (e.g. base64url of '[1,2,3]') instead of an object.","commonSituations":"Corrupted/truncated payload segments; custom-issued tokens with array payloads; pasting the wrong base64 string into the payload slot in tests; buggy custom token generators.","solutions":["Verify the payload decodes to a JSON object: check for '{' after base64url-decoding","Reject the token as malformed by catching InvalidTokenStructure","Fix the token issuer — payload must be json_encode of an associative array","Manually decode segments during debugging to confirm which part is wrong"],"exampleFix":"// before\n$token = $parser->parse($jwt); // payload decodes to 'null'\n// after\n$payload = SodiumBase64Polyfill::base64UrlDecode(explode('.', $jwt)[1]);\nif (!str_starts_with(trim($payload), '{')) {\n    throw new InvalidArgumentException('JWT payload must be a JSON object');\n}\n$token = $parser->parse($jwt);","handlingStrategy":"validation","validationCode":"$p = SodiumBase64Polyfill::base64UrlDecode(explode('.', $jwt)[1]); if (!is_array(json_decode($p, true))) { throw new InvalidArgumentException('Claims are not a JSON object'); }","typeGuard":null,"tryCatchPattern":"try { $token = $parser->parse($jwt); } catch (Lcobucci\\JWT\\InvalidTokenStructure $e) { return error_401('Malformed token claims'); }","preventionTips":["Ensure the issuer encodes an associative array as payload","Reject empty/null payloads early","Catch InvalidTokenStructure in one place (middleware) for all parser calls"],"tags":["jwt","parser","claims","php"],"backgroundTag":"unexpected-response-shape","analyzedSha":"375813049c24c7111bda8b6884c57b071ceb2fe7","analyzedAt":"2026-09-14T11:12:28.004Z","contentChangedAt":"2026-09-14T11:12:28.004Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}