The answer by Florent leaves me unsatisfied.
Regarding the example of using a JWT to sign a hash of a document... the assertion is that the algorithm and keyID would be "sensitive data" that needs to be "protected". By which I suppose he means "signed". But there's no need to sign the algorithm and keyID.
Example
Suppose Bob creates a signed JWT, that contains an unprotected header asserting alg=HS256 and keyid=XXXX1 . This JWT is intended for transmission to Alice.
Case 1
Suppose Mallory intercepts the signed JWT sent by Bob. Mallory then creates a new unprotected header, asserting alg=None.
The receiver (Alice) is now responsible for verifying the signature on the payload. Alice must not be satisfied with "no signature"; in fact Alice must not rely on a client (sender) assertion to determine which signing algorithm is acceptable for her. Therefore Alice rejects the JWT with the contrived "no signature" header.
Case 2
Suppose Mallory contrives a header with alg=RS256 and keyId=XXX1. Now Alice tries to validate the signature and finds either:
- the algorithm is not compliant
- the key specified for that algorithm does not exist
Therefore Alice rejects the JWT.
Case 3
Suppose Mallory contrives a header with alg=HS256 and keyId=ZZ3. Now Alice tries to validate the signature and finds the key is unknown, and rejects the JWT.
In no case does the algorithm need to be part of the signed material. There is no scenario under which an unprotected header leads to a vulnerability or violation of integrity.
Getting Back to the Original Question
The original question was: What is the purpose of an unprotected JWT header?
Succinctly, the purpose of an unprotected JWS header is to allow transport of some metadata that can be used as hints to the receiver. Like alg (Algorithm) and kid (Key ID). Florent suggests that stuffing data into an unprotected header could lead to efficiency. This isn't a good reason. Here is the key point: The claims in the unprotected header are hints, not to be relied upon or trusted.
A more interesting question is: What is the purpose of a protected JWS header? Why have a provision that signs both the "header" and the "payload"? In the case of a JWS Protected Header, the header and payload are concatenated and the result is signed. Assuming the header is JSON and the payload is JSON, at this point there is no semantic distinction between the header and payload. So why have the provision to sign the header at all?
One could just rely on JWS with unprotected headers. If there is a need for integrity-protected claims, put them in the payload. If there is a need for hints, put them in the unprotected header. Sign the payload and not the header. Simple.
This works, and is valid. But it presumes that the payload is JSON. This is true with JWT, but not true with all JWS. RFC 7515, which defines JWS, does not require the signed payload to be JSON. Imagine the payload is a digital image of a medical scan. It's not JSON. One cannot simply "attach claims" to that. Therefore JWS allows a protected header, such that the (non JSON) payload AND arbitrary claims can be signed and integrity checked.
In the case where the payload is non-JSON and the header is protected, there is no facility to include "extra non signed headers" into the JWS. If there is a need for sending some data that needs to be integrity checked and some that are simply "hints", there really is only one container: the protected header. And the hints get signed along with the real claims.
One could avoid the need for this protected-header trick, by just wrapping a JSON hash around the data-to-be-signed. For example:
{
"image" : "qw93u9839839...base64-encoded image data..."
}
And after doing so, one could add claims to this JSON wrapper.
{
"image" : "qw93u9839839...base64-encoded image data..."
"author" : "Whatever"
}
And those claims would then be signed and integrity-proected.
But in the case of binary data, encoding it to a string to allow encapsulation into a JSON may inflate the data significantly. A JWS with a non-JSON payload avoids this.
HTH