自动摘要
正在生成中……
Most APIs today use an API Key to authenticate legitimate clients. API Keys are very simple to use from the consumer perspective:
- You get an API key from the service (in essence a shared secret).
- Add the key to an
Authorization
header.
- Call the API.
It can't get simpler than that, but this approach has some limitations.
The last couple of months, we've been working on our API v2. We wanted to share what we've learnt implementing a more powerful security model using JSON Web Tokens.
Using a JSON Web Token offers many advantages:
- Granular Security: API Keys provide an all-or-nothing access. JSON Web Tokens can provide much finer grained control.
- Homogenous Auth Architecture: Today we use cookies, API keys, home grown SSO solutions, OAuth etc. Standardizing on JSON Web Tokens gives you an homogenous token format across the board.
- Decentralized Issuance: API keys depend on a central storage and a service to issue them. JSON Web Tokens can be "self-issued" or be completely externalized, opening interesting scenarios as we will see below.
- OAuth2 Compliance: OAuth2 uses an opaque token that relies on a central storage. You can return a stateless JWT instead, with the allowed scopes and expiration.
- Debuggability: API keys are opaque random strings. JSON Web Tokens can be inspected.
- Expiration Control: API keys usually don't expire unless you revoke them. JSON Web Tokens can (and often do) have an expiration.
- Devices: You can't put an API key that has full access on a device, because what is on a phone or tablet can easily be stolen. But you can put a JWT with the right set of permissions.
If you'd like an in-depth introduction to JSON Web Tokens, check out our free ebook below.
Granular Security
One of the most interesting benefits of using JWTs is the first one listed above. Back in the old days, when databases were at the center of our client-server applications, we could create users with specific permissions on the database:
Remember this?
APIs are becoming central pieces of our distributed systems architecture. They are now the "gatekeepers" of our data. But in contrast with what was available in databases, virtually all API keys provide all-or-nothing access. Readers will likely be familiar with the scope
parameter of OAuth2 based systems that offers this finer grained consent to access.
There are many situations in which you want to keep the simplicity of an API Key but only for a subset of all possible API operations.
GitHub acknowledged this requirement and now provides a way of creating a token with the scopes you need (mimicking the OAuth2 consent):
In the next section, we will go through the details of how this can be implemented.
How to implement it?
We don't know how GitHub implemented it (they probably used Ruby), but we will use it as an example. Let's say we want to implement an endpoint in the API to create new repos. You could model this with the following JSON Web Token payload. If you provide any of those scopes, then you can create repos:
{
iat: 1416929109, // when the token was issued (seconds since epoch)
jti: "aa7f8d0a95c", // a unique id for this token (for revocation purposes)
scopes: ["repo", "public_repo"] // what capabilities this token has
}
Look at this token in jwt.io
The API endpoint would simply check for the presence of the right scope atribute (this example is written in node.js but any language would work):
app.use('/api', express_jwt({secret: SECRET, userProperty: 'token_payload'}));
app.post('/api/user/repo',
check_scopes(['repo', 'public_repo']),
function(req, res, next) {
....
});
Notice the check_scopes
middleware on the /api/user/repo
route. This is how the check_scopes
function is implemented:
function check_scopes(scopes) {
return function(req, res, next) {
var token = req.token_payload;
for (var i =0; i<token.scopes.length; i++){
for (var j=0; j<scopes.length; j++){
if(scopes[j] === token.scopes[i]) return next();
}
}
return res.send(401, 'insufficient scopes')
}
}
Notice that no one can change the scopes variables. JWTs are digitally signed, so its content cannot be tampered with.
Documenting an API is equally important. What would be a good way for surfacing this on an API explorer?
For Auth0, we decided to build our own documentation using swagger. Since we are a multi-tenant system, each tenant has an API Key and Secret that is used to sign the token. As a developer, you mark which scopes you need and a token will be auto-generated. You can copy and paste it to jwt.io to see the structure (this is the debuggable piece, by the way).
Scopes required by each operation are clearly identified:
Our token format is a bit different from the one in the example we showed for GitHub. The good thing about JWTs is that they can contain any data structure:
{
iat: 1416929061,
jti: "802057ff9b5b4eb7fbb8856b6eb2cc5b",
scopes: {
users: {
actions: ['read', 'create']
},
users_app_metadata: {
actions: ['read', 'create']
}
}
}
The string representation of the scope is read:users
but in the JSON Web Token, we are using a more structured representation (note the hierarchy), this allows us to be more consistent. It also allows us to have an easy way to extend it for other scenarios.
Conclusion & Resources
We have shown a new way of securing APIs based on JSON Web Tokens. This approach has some interesting benefits, in particular around granular security. In future posts we will go over other aspects, starting with how to revoke tokens.
Below are some of the resources that we used in our node.js backend implementation:
If you want to see an example of this working, you can check out our API v2: https://docs.auth0.com/apiv2
We would love to hear your thoughts about this approach!
Discuss here
Aside: Delegating JWT Implementation to the Experts
JWTs are an integral part of the OpenID Connect standard, an identity layer that sits on top of the OAuth2 framework. Auth0 is an OpenID Connect certified identity platform. This means that if you pick Auth0 you can be sure it is 100% interoperable with any third party system that also follows the specification.
The OpenID Connect specification requires the use of the JWT format for ID tokens, which contain user profile information (such as the user's name and email) represented in the form of claims. These claims are statements about the user, which can be trusted if the consumer of the token can verify its signature.
While the OAuth2 specification doesn't mandate a format for access tokens, used to grant applications access to APIs on behalf of users, the industry has widely embraced the use of JWTs for these as well.
As a developer, you shouldn't have to worry about directly validating, verifying, or decoding authentication-related JWTs in your services. You can use modern SDKs from Auth0 to handle the correct implementation and usage of JWTs, knowing that they follow the latest industry best practices and are regularly updated to address known security risks.
For example, the Auth0 SDK for Single Page Applications provides a method for extracting user information from an ID Token, auth0.getUser
.
If you want to try out the Auth0 platform, sign up for a free account and get started! With your free account, you will have access to the following features:
To learn more about JWTs, their internal structure, the different types of algorithms that can be used with them, and other common uses for them, check out the JWT Handbook.