To render our geometry and take advantage of our normals, we require a shader that implements the lighting equation. This is a more sophisticated application of GLSL. Here is a summary of GLSL that covers everything we'll need to know.
The uniforms configure the transformation matrices and the lighting parameters. The attributes configure the vertices. The varyings define the values to be interpolated by the rasterizer. Pay close attention to their types.
The vertex shader uses the scene parameters defined by the attributes and uniforms to calculate $n$, $l$, and $v$, the inputs to the lighting model. Note, lighting calculations are done in eye space because this allows us to infer that the viewer sits at the origin.
The lighting equation itself is implemented in the fragment shader. Here are the inputs.
And here is the calculation. We arbitrarily choose a constant specular exponent of 10, but this could easily be another uniform.
As usual, don't forget to use
gl.uniform4f in the application code to query the locations of each of these uniforms and give them a value.