Great article! We've updated the OpenAI API to 403 on HTTP requests instead of redirecting.
$ curl http://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer 123" \
-d '{}'
{
"error": {
"type": "invalid_request_error",
"code": "http_unsupported",
"message": "The OpenAI API is only accessible over HTTPS. Ensure the URL starts with 'https://' and not 'http://'.",
"param": null
}
}
The HSTS header is only effective when it's received over HTTPS. And if it has taken effect, the client won't try to access HTTP anymore, so it won't even know what response it would have gotten from HTTP.
What about this then? When the request is made over insecure HTTP, revoke the API key used, but then send the usual redirect response for HSTS. Then, when/if the request gets repeated over HTTPS, notice the key is revoked and so respond to that one with 403.
If your goal is to waste people's time, cause them to question their sanity, and guarantee that they're way too pissed off when they finally figure out what happened that instead of teaching others about how important it is too use HTTPS from the start, they talk about how awful your API is and how much they hate your company for a terrible design, then yes this sounds like a good plan.
If it were a generic 403, sure. But if the 403 message said something to the effect of "this API key is no longer valid because it has previously been observed over insecure HTTP", then wouldn't that be fine?
Yes agreed, I think that would improve it, although depending on the situation it may still cause substantial pain. Also apologies, after re-reading my previous comment it seemed unnecessarily harsh toward you, though that wasn't my intention!
On the subject though, an example of how it could cause a lot of pain, many bigger corps don't allow developers to have "real" API keys, and they certainly can't generate new ones themselves, so this might mean one slip-up with curl results in at best a ticket with another team. It also might end up bringing down production in a horrible way, for example if the dev or an ops person is debugging and curls the endpoint from a pod in prod and forgets to explicitly put the https, curl will default to http which would then immediately cause the prod key to be revoked with no second chances. That could even happen on a GET request, which normally GETs are supposed to be safe/side-effect free! If you're operating at a big scale, that could be utterly disastrous, causing a widespread production outage immediately.
If it's a dev that is just testing a key locally that isn't used anywhere else, then it's obviously less of an issue, but taking that into account starts to balloon the complexity around your token revocation code.
Posting on this forum means you are probably not J. Random User. I mean specifically anyone who will not grasp the difference between http:// and https://api.example.com.
I've done that myself and have consumed many others who have done it, and I don't think it's better. Much better to get a response that tells you to use https for the API. (for browser also a redirect is a must for UX, though our context here is API)
Thank you for sharing! I think this sort of thing is what makes HN great.
Have you rolled this out to prod yet? Did you check how many users this might effect? I can imagine some (probably amateur) apps are going to break when this hits, so some notice might be nice.
I'm not asking those questions critically, mainly wanting to facilitate a full discussion around the pros and cons (I think the pros are are much stronger personally).
Not listening on port 80, such that the user gets a connection refused, would result in the client not sending the api key over the wire at all.
I personally think listening, accepting that user mistakes can expose API keys to MITMs, and returning the user-facing error is better than a "connection refused" error, but it is a tradeoff.
Why do you think 403 is the wrong error code? Based on the spec it seems entirely appropriate to me:
> HTTP 403 provides a distinct error case from HTTP 401; while HTTP 401 is returned when the client has not authenticated, and implies that a successful response may be returned following valid authentication, HTTP 403 is returned when the client is not permitted access to the resource despite providing authentication such as insufficient permissions of the authenticated account.[a]
> Error 403: "The server understood the request, but is refusing to authorize it." (RFC 7231)
I thought the best response from the article was the 426 Upgrade Required. That way you can throw in an Upgrade: https field in the response. It makes it immediately clear that something weird is going on. 403s are common, my first thought would be a badly-configured API key which lacks the expected permissions.
You do not throw 403 if the client is authorized to access whatever resource it's trying to.
The 426 on the sibling comment is great, though. But if you don't find an error code for your case, you don't go and redefined a well defined one. You use 400 (or 500).
400 is usually for a malformed request. It seems like in this case the request is well formed, it's just not allowed. 403 seems reasonable if the user isn't authorized to make a request to the URL, which they aren't. Some APIs return redirects which also seems pretty reasonable.
But that also implies that some user would be authorized to make a request to the HTTP port (or that the resource does exist, which in this case it doesn’t).
IMO, 400 is more accurate, but really either could be acceptable, so long as the client is notified of the error. But, I wouldn’t automatically redirect the client. That’s what we are trying to avoid.
I guess this might depend a little on the implementation. In some cases the http endpoint may exist but may only be accessible to a sidecar container via localhost. For example, if the sidecar terminates https.
Not sure how I feel about this (extremely arbitrary) distinction. 400 Bad Request maybe implies that no matter how many times you retry the request, it will never succeed. 403 Forbidden maybe implies that some external change in the authentication system could perhaps allow the same request to succeed in the future? So I guess in that lens I can see the logic, but again, seems extremely arbitrary.