I got normal mapping to work, but only after changing a line in the fragment shader to match other peoples code:
vec3 tangent_space_normal = 2 * texture(normal_map, in_texcoord).xyz - 1;
What is the purpose of multiplying the sampled normal by 2 and subtracting 1?
For instance, Sasha Willems does it like that here (Shaders section, near bottom of page):
Sasha Willems GLTF Scene Rendering
I’m using GLTF 2.0 and Blender.