How-to comply with HTTP standard when putting the API key in a header

Not applicable

Right now, we only allow clients to send API keys to our Edge proxy using only the apikey query parameter.

This makes it difficult for clients to keep their APIkeys secret, they tend to leak keys on a regular basis. Both ourselves and some of our clients believe it's a good idea to support passing API keys in the headers, where they will benefit from the security of HTTPS, although we will also continue to support the key as a query parameter.

I started investigating how to do this correctly, and reading the HTTP specs I think a correct way to do this would be to use the authorization header, presumably something like:

Authorization : APIKEY your-api-key-here

I notice this is not the way that is suggested in your documentation. Is there a reason for that (eg. client ease-of-use)? What are the advantages / disadvantages using the apikey header vs the Authorization header?

I also notice that default status response for an invalid apikey parameter is HTTP 401, unauthorized. However, this requires that the response headers include a suggested authentication method, which is also not configured. Are there any consequences to not providing this header?

1 12 64.7K
12 REPLIES 12

Not applicable

We use the header param X-Merck-APIKey which seems to be a defacto convention.

Hi @Kurt Kanaskie

regarding that naming convention, you may wish to have a quick look at https://tools.ietf.org/html/rfc6648 . Apparently X- prefixes are soooo 2011.

Oh man I'm such a noob.

From the spec, for private use, Merck-APIKey would make me hip 🙂

There is no single, "correct" way to pass in authentication information including APIKeys. This is true for any HTTP API, whether managed in Apigee Edge or not.

As you know, your policies in Apigee Edge can access any part of the inbound message: query parameters, headers, payload, url path elements, and methods.

You can choose any one of those options.

You mentioned "key leakage" as a problem. for sure, eliminating the possibility to transmit keys in the query string is a good idea. Also, using TLS to secure the transport layer may also be recommended.

As for whether using the Authorization header WITH an APIKEY prefix is necessary. The spec recommends it, so obviously it's not *wrong*, but it does seem like an extra bit of formality and strictness that isn't contributing any value to either the caller or the receiver. And maybe it is contributing some "cost" in terms of complexity. In particular on the caller side, the developer would need to add that prefix to every call. (one extra string concatenation). On the server side, you would need the converse - a string split - and then you'd need to validate that the prefix was the prefix you were expecting: Apikey and none other. What is the point of all this extra framing? To me, the simplest thing that works is the best, and this APIKEY prefix would represent extra framing.

The prefix seems like it would be a really good idea if your API endpoint needed to handle multiple distinct kinds of authentication: Key, Token, Signature, and so on. And all of them were alternatives, that could be requested via different values in the same request header. There, the prefix disambiguates the remaining payload of the header. However, if your API doesn't support multiple auth values, then it just seems like an unnecessary extra bit of protocol.

You may say: what if, in the future, I would like to introduce a new authentication mechanism, distinct from APIKey? Wouldn't it be smart to use the prefix now to allow this future possibility? Here, I think YAGNI applies. Even if you implement an extension like this in the future, you can introduce prefixes THEN, and any header with no prefix, just treat as an APIKey by default. So there's still no problem. And if the purist in you insists on always including a prefix if any of the Authentication headers require a prefix, why, you could simply increment the API revision to handle that case.

So in summary, "meh" on the APIKEY prefix.

The counter-argument is obviously "OAuth v2 uses the Bearer prefix". Quite true, but all of my reasoning applies there as well. It's still unnecessary. But there are lots of tools that know how to build or parse "standard" OAuthV2 headers, so that prefix will likely remain. (In Apigee Edge, the policy that verifies an OAuthV2 token by default expects to find and strip out the Bearer prefix, but you can configure it to expect a different prefix, or no prefix).

Regarding rfc7235 and the recommendation to use the WWW-Authenticate response header, surely that is a good idea. That is a good practice which has been in use for some time. However, if you document your API correctly, it is not strictly necessary. You can simply state in the documentation how the caller needs to authenticate (pass a key, sign the request, etc), and a 401 Unauthorized need not be transmitted with additional information in the case of an authentication failure.

In other words, It might be developer-friendly to distinguish between a 401 because of a missing key, and a 401 because of an expired key, and a 401 because of an present-but-invalid key, but it is not strictly necessary.

@tpearson - I couldn't understand how HTTP headers are more secure than query parameters? In TLS, both of them are encrypted.

Not applicable

The same security mechanisms apply to query params as header values. Having said that, you do get a bit of warm fuzzy feeling having anything like a key not appear as a query param because as web uses we are so accustomed to seeing them on a URL - and we don't see headers. There is also a greater likelihood of leaking an API key in a log file when it is part of the URL.

HTTP Watch has nice blog post on this topic that you might enjoy: http://blog.httpwatch.com/2009/02/20/how-secure-are-query-strings-over-https/

Not applicable

Thanks everyone for the healthy debate.

@David Allen and @Ozan Seymen - thanks for the article and the tip. I had assumed that, because it was in the URL, the query parameter was not encrypted. Good to know that I'm wrong, that makes this task less urgent.

@Dino, thanks for the comprehensive response. We do actually use a second form of auth in some of our APIs, a legacy login system used by some of our backends. For this, we use the Authorization header with the LSS prefix. If I were to use the Authorization header for API keys, I think I would stick to the standard and add the APIKEY prefix, even though it does hurt ease-of-use.

Otherwise, if I'm going to ignore the sandard and focus on ease-of-use, a header along the lines of

Apikey: your-api-key-here

probably makes the most sense - it's simple, and it's consistent with the query parameter.

I don't have a preference right now, I'll think about if for a few days. Feel free to let me know your opinions on why I should choose one or the other.

@tpearson - everything apart from hostname and port (and IP) is encrypted. TLS sits on top of TCP so all HTTP protocol specific data is encrypted.

Check the 3rd paragraph under Overview heading in https://en.wikipedia.org/wiki/HTTPS.

Not applicable

So, we looked into this some more. Taking an adhoc survey of how details are passed in our favorite APIs (Google Maps, Facebook Graph, Twitter, Amazon, Stripe, Yelp...) we found about 50% passing credentials and keys in a HTTP Authorization header, 30-40% passing keys in query parameters, and the rest either using HTTP basic or a custom header. So, clearly, a custom header is not the way to go. Either it goes in the query parameters, or in the Authorization header. Some APIs support both, presumably for those who haven't given up on JSONP.

The problem is, looking in the documentation, I don't see an obvious way to use the VerifyAPIKey policy with the Authorization header. Would I have to use a JS call (or something else) to locate the APIKey token manually and then bind the key into another variable for the VerifyAPIKey policy to check it? Or is there a better way?

@Dino, @David Allen, @Ozan Seymen- no suggestions?

Hi @tpearson

Regarding your first paragraph - yes, unfortunately there is no "standard" way of doing this especially if we are talking about HTTP standard. This is left to the "imagination" and/or "personal preference" of developers. Everybody has their personal preference in this matter.

The only "technical" comment that I can make is - if you have apikeys in queryparams, there is a chance that it will be logged in http servers and/or intermediaries that the request is passing through. So it is something for you to think about if you are ok to have that (sometimes it is beneficial to have that for logging purposes, sometimes it is totally undesirable by security teams)

Coming to the second half of your comment, you haven't mentioned the structure of your Authorization header but if I assume you are directly putting the value in, e.g. Authorization: abcd123, then in Apigee, you can do request.header.Authorization. This variable will get the value of the apikey. So your policy will look like:

<VerifyAPIKey name="xyz">
    <APIKey ref="request.header.Authorization" />
</VerifyAPIKey>

If you are using any custom Authorization header extensions as it is described in in HTTP spec, then you might need to write a simple JS to extract the data yourself.

Ouch. So if I want to support an Authorization header of the format matching the specs like

Authorization Apikey jeafkpejaffeajipfeja

as well as an apikey query parameter for backwards compatability. I have to

  1. Write the apikey query parameter into the Authorization header, if it doesn't already exist
  2. Parse the Authorization header to find the Apikey Authorization token, in case there are other tokens, and copy its value to a variable
  3. Verify that variable using the VerifyAPIKey policy

Does that sound correct?