It was one of those days where you start with a simple task, find a freak issue, end up reading an RFC for 4 hours and your mind starts thinking about the famous talk “The mess we’re in” by Joe Armstrong as you go to bed.
In my case I was trying to setup logic for refreshing authentication tokens when a response with 401 status code is received. I was working on the rather radical experiment we’re doing with Flutter and I needed to setup the authentication layer for it.
I went about it as I usually do, wrote the logic and added tests to ensure the code was doing what I expected it to do.
Then I went ahead and tried it out, just to get an exception “HTTPException: failed to parse header”. I was expecting a 401 response and my refresh token logic should have been triggered but none of that happened. Now this is where the journey down the rabbit hole started.
I quickly tried curl with an intentionally expired token. I got the following response and nothing seemed to be wrong.
HTTP/1.1 401 Unauthorized
Server: openresty/1.13.6.2
Date: Fri, 05 Mar 2021 07:20:37 GMT
Content-Type: application/json
Transfer-Encoding: chunked
{
"errors": {...}
}
I then went ahead and added some debug points to figure out where this exception was being raised. But due to the async work this involved, the debugger was not taking me to the actual site of the exception nor was it giving me a complete stacktrace.
I then did a search and found an open issue in dart/http package where someone had mentioned about a header called WWW-Authenticate.
I then headed over to MDN to read about 401 responses & the WWW-Authenticate token. Then I went over to RFC 7235 and read over the parts which talks about this. I learnt that:
A server generating a 401 (Unauthorized) response MUST send a WWW-Authenticate header field containing at least one challenge.
This is what was missing in the response I got above. This was probably causing some exception to be raised by the Dart HTTP client which was rightly expecting this header.
Then I turned back to curl to verify again. This time I just entered some random text as the auth token. To my surprise, there was the header which was previously not present.
HTTP/1.1 401 Unauthorized
Server: openresty/1.13.6.2
Date: Fri, 05 Mar 2021 07:20:37 GMT
Content-Type: application/json
Transfer-Encoding: chunked
WWW-Authenticate: Bearer realm=<some_value>, error=invalid-token, error_description=Invalid
bearer token
Perfect, I said to myself. I might have missed this header before. Let’s try running the app again, it should work now that the header is present… Same exception !!
Turns out the server is not consistent in setting the header.
But why did fail when the token existed? I went back to the issue on dart/http and found that someone had mentioned that it’s probably because the library cannot parse the token because of the whitespaces present in the value of the header.
So the mess I’m in right now is that all the potentially correct solutions are larger in scope than my entire “radical” experiment:
- Figure out how to fix this in the dart’s HTTP package as the issue has been open since 2 years.
- Convince the backend team to remove whitespaces from the header value and consistently send it for every 401.
- Find a different HTTP library for Dart (I haven’t found any option yet).
- Implement my own HTTP client library for Dart.
The bad solution - treat the exception as a 401 and not say anything about it ever again. The mess we’re in…