How we implemented API keys for prefix!
Along with private and public channels, prefix.dev now supports API Key authentication! In this blog post, we share a few details on how we implemented API Keys with security in mind.
What are API Keys
API Keys are a widely used means of authenticating API requests. They are in use at many large developer platforms such as GitHub, Docker or Anaconda.org. Since we now support uploading packages to public or private channel, we definitely needed the means to authenticate with the platform from a CLI tool or similar.
The conda / mamba community has two ways of authenticating against an API (built-in to the tools):
- HTTP Basic auth: this is the
https://user:password@mypage.com/...
authentication where theuser:password
is sent as aAuthorization: Basic <hash>
. This is a proven and reliable way of authenticating against an API but doesn't seem to be very much in fashion anymore - The conda / Anaconda token: this is a very weird way of authenticating
against an API. It injects a token into the URL like
https://anaconda.org/t/<TOKEN>/rest/of/url.tar.bz2
. Unfortunately, it's not really "standard" and sending tokens as part of the URL is not very secure.
We've decided to learn from the best and implement a more standard way of
sending the API Key, namely under the Authorization: Bearer <TOKEN>
- a so-called "bearer token".
How do API Keys work
API Keys do two things: identify who is the owner of the API Key and a
password. For the prefix.dev keys, we've chosen a common prefix (pfx_
) on all
keys to make them easily identifiable (we hope that this will also help with
security scanning later on!). We then use the first 8 characters of the key as
the ID of the key (to uniquely identify it) and the rest of the key is the
"password".
This is what a prefix.dev API key looks like:
We use "nanoid" for the key because it is relatively "readable" and more compact vs. UUID4 or other formats. You can read more about nanoid vs UUID in the PlanetScale blogpost.
The second important part of the API key implementation is that they should be treated like a password. That means they should never be stored in plain text! This is also the reason why you have to store the API key yourself and can only see it once in the prefix.dev UI - we literally have no means of retrieving the information once we sent it to you.
Instead of storing the password-part of the API key we store a hash of the
password. This is a very common technique, and usually people use
bcrypt
for this purpose. Another more
modern alternative, that won some password-hashing competitions, is
argon2
. There is a convenient
argon2
Rust crate for it that
makes it easy to hash and verify the passwords.
The code in our backend looks roughly like this:
What to do with the API key
Now that you know how our API keys roughly work, what can you do with them?
Well, globally authenticate against our API endpoints! If you supply the
Authorization: Bearer <API KEY>
header, you can authenticate against the
GraphQL and REST endpoints on the prefix.dev platform.
To read more about our endpoints, you can check out the documentation:
To make an authenticated request with curl
against our GraphQL API, you could
use the following snippet:
Or with python/requests
, for the same:
Future work
We have already integrated support for the "BearerToken" format into
micromamba
- you can login using micromamba auth login https://repo.prefix.dev --bearer pfx_vr2XPfxpByKvGVhzrkjtrjn235nj23n4jn9v
. We
are currently in the process of adding authentication support (for all three
described authentication methods) to rattler
, so that rattler can be used for
authenticated repodata retrieval and package installation easily. For this, we
extended the reqwest
client into an AuthenticatedClient
.
Furthermore, we hope that we can also make a conda plugin available that
implements the Bearer
-Token authentication method. The conda team has done a
great job at making the conda codebase extensible, and I believe that the
request/channel layer is already extensible, so this might already be possible.
Maybe even anaconda.org will switch to a more standard "Bearer"-Token
authentication in the future?
We hope that this article illustrated how serious we take security. If you try out our authenticated API or our newly released private channels, do let us know. We love to hear from users on our Discord or on the repository.