API Security with JWT claims.
Tags: aws, auth0, cloudcomputing, jwt
Published: 2023-05-23 19:17:23

Token-based authentication with JWT (JSON Web Tokens) is a lightweight and flexible method to enhance security in cloud applications.

Let's review a few fundamental concepts.

> If you are already familiar with JWTs and only interested in learning how to consume them, feel free to skip ahead to the next section.

A JWT consists of three parts: the header, payload, and signature. These parts are encoded and concatenated using base64 encoding, separated by periods ('.'). The structure of a JWT is as follows: `header.payload.signature`.

The header specifies the token type (JWT) and the signing algorithm used to generate the signature. It is a JSON object encoded in base64. For example:

```
{
  "alg": "HS256",
  "typ": "JWT"
}
```

The payload contains claims or statements about the entity (user, application, etc.) and additional data. It is another JSON object encoded in base64.

```
{
  "sub": "1234567890",
  "name": "John Doe",
  "scopes": ["read:users", "write:posts"]
}
```

The signature is generated by taking the base64-encoded header and payload, concatenating them with a period ('.'), and signing the resulting string using a secret key or private key. The signature ensures the integrity and authenticity of the token. The specific algorithm used for signing is defined in the header. For example:

```
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secretKey
)
```

The final result will look something like this:

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```

## How do we use JWTs?

JWTs provide a stateless mechanism for securely transmitting information between parties. They are widely used for authentication and authorization in distributed systems.

Let's consider a scenario where you want to access a website or an app on your phone.

1. When you enter your username and password, the app creates a unique token that identifies you.
2. The app saves this token and includes it with any requests you make.
3. Each time you make a request the token is validated.

> Note: The token usually contains embedded information (claims) that allow the app to determine your identity and permissions without needing to check a central database for each request.

### Getting started

One of the easiest ways to begin with token-based authentication is to leverage an authentication provider like [Auth0](https://auth0.com/) and use infrastructure provisioning tool such as [Nitric](https://nitric.io) or [Terraform](https://www.terraform.io/) to deploy an API Gateway solution from your preferred cloud provider.

When you configure a JWT authorizer for a route in your API Gateway with major cloud providers like AWS, GCP, or Azure, the JWT is automatically validated, and requests are allowed or denied based on the token's validity.

#### Resource-based security with scopes

In the previous example of the payload, we used the "scopes" claim. Scopes define the specific actions or resources that the entity can access. The "scopes" claim in a JSON Web Token (JWT) is commonly used to represent permissions granted to the authenticated entity.

Before you start coding, you need to determine the scopes relevant to your application. For example, you might have scopes like "read:products" or "write:products" to represent different levels of access. You can then apply security rules to the entire API by including them in the security definition.

```
import { api, jwt } from "@nitric/sdk";

const prodApi = api("productInventory", {
  securityDefinitions: {
    user: jwt({
      issuer: "https://your-auth0-app.region.auth0.com",
      audiences: ["api.example.com"],
    }),
  },
  security: {
    user: ["read:products"],
  },
});

prodApi.get("/product/:name", async (ctx) => {
  const { name } = ctx.req.params;

  // Write code to fetch product specifications
  ...
  ...

  ctx.res.body = `Your product is: ${name}`;
  return ctx;
});
```

In this example, the endpoint `/product/` checks if the "read:products" scope is present in the "scopes" claim of the JWT payload. If the scope is found, the handler is executed to fetch product information. Otherwise, it returns an error response indicating insufficient privileges.

By utilizing the "scopes" claim, you can enforce fine-grained authorization based on the authorized actions or resources defined in the JWT.

The example also includes the "audience" claim, which ensures that the JWT is only accepted by the resources listed in the security definition. You can refer to the [AWS documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html) for the full list of claims that are validated by default for AWS.

> Note: Nitric automates most of the configuration required to correctly set up API Gateways for automatic token validation. You can choose to set up the same functionality with other API frameworks like Express and deploy it using IaC tools like Terraform. In that case, you would be responsible for configuring everything from scratch.