# tokien

> Internal library for the Salling Group API team to issue and verify access tokens for the API Gateway.

Latest version **1.0.0** (published 2021-03-12) · 0 weekly downloads

## Install

```sh
npm install tokien
pnpm add tokien
yarn add tokien
bun add tokien
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.0.0 |
| Published | 2021-03-12 |
| First published | 2021-03-12 |
| Weekly downloads | 0 |
| TypeScript types | none |
| Module format | CommonJS |
| Node | 14.15.4 |
| Dependencies | 0 |
| Unpacked size | 32.1 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | apidevs |

## Links

- npm: https://www.npmjs.com/package/tokien
- npm.io page: https://npm.io/package/tokien

## Recent versions

- 1.0.0 (latest) — 2021-03-12

## README

# Tokien

Internal library for the Salling Group API team to issue and verify access tokens for the API Gateway.

## How to use

1.  Ensure you're running at least node 12 before proceeding. The library does not work with node versions before 12.
2.  Install the library from NPMJS.com:

    ```bash
    npm i @dansk-supermarked/tokien
    ```
3.  Require the library in your code:

    ```javascript
    const { 'v1': { issue, verify } } = require('@dansk-supermarked/tokien');
    ```
4.  Issue or verify tokens as needed.

    ```javascript
    const { deepStrictEqual } = require('assert');
    const payload = {
      ctx: {
        id1: '123',
        id2: '234',
      },
      env: [ 'v1-test', 'v1-dev'],
      exp: new Date().getTime() + 3600000,
      id: 'my-project',
      sub: '345',
    };
    const myKey = Buffer.from('a super secret key from env vars');
    const token = issue(payload, myKey);
    const verifiedDecryption = verify(token, myKey);
    deepStrictEqual(payload, verifiedDecryption);
    console.log(token);
    ```

## `encrypt`

The `encrypt(plaintext, key)` function is a lower-level function that encrypts any given plaintext string without performing any validation of what's in the string. This is a building block used for the `issue` function. The `encrypt` function does not add the `"sg.v1."` prefix to its output, but returns just the Base64URL-encoded ciphertext.

## `decrypt`

The `decrypt(ciphertext, key)` function is a lower-level function that attempts to decrypt any given ciphertext string without parsing the result as JSON or performing any validation of what's in the resulting string. This is a building block for the `verify` function. The `decrypt` function expects just the Base64URL-encoded ciphertext without the `"sg.v1."` prefix as input and returns the original plaintext as-is.

## `issue`

The `issue(payload, key)` function takes two arguments:

### Payload (Object)

The payload must be a regular Object with the following keys. Additional keys are allowed and will be included in the token as well as in the decryption of the token.

Key | Type | Examples | Description
-- | -- | -- | --
`env` | Array of Strings | `["prod"]`, `["test","dev"]` | The environments that the customer is allowed to access. Typically takes the form `prod`, `test`, etc. Note that the environment is _not_ the version. E.g. `"v1-test"` is both a version and an environment. This field is used for the environment. Each access token may grant access to many different versions of APIs without too much of a security risk, but it's very important that we can pin the _environment_.
`exp` | Number | `new Date().getTime() + 3600000` | The timestamp (in milliseconds since epoch) that the token expires. Can NOT be in the past. Can NOT be more than 366 days ahead. In general you probably want to issue short-lived tokens with a lifespan of about one hour.
`id` | String | `"foetex-hd"` | The ID of the access rights template that this token grants the customer access to. The templates live elsewhere, in our [api-products service][api-products], and it's expected that whoever consumes the access token has access to look up the template details from that service.
`sub` | String | `"b51fa1b52ebe44afb2c18fd6bcf060d7"` | The DS ID of the customer that this token is valid for. Since this token is on behalf of a customer, we must know which customer, and the canonical ID is the DS ID / Gigya UID / SAP Customer Cloud UID (all the same thing).
`ctx` | _(Optional)_ Object | `{"hdfot":"123"}` | Any additional information that should be made available to the route template, like additional IDs. May be omitted. If present, this must be an Object with String values.

### Key (Buffer)

The key must be an instance of [Buffer][buffer] and must have a length of exactly 32 bytes.

## `verify`

The `verify(token, key)` function takes two arguments:

### Token (String)

A string returned by the `encrypt` function.

### Key (Buffer)

The key must be an instance of [Buffer][buffer] and must have a length of exactly 32 bytes.

## Token Format

The resulting encrypted token has the following format:

`{SALLING GROUP PREFIX}.{TOKEN VERSION}.{ENCODED CIPHER}`

where

*   `{SALLING GROUP PREFIX}` is always `sg`.
*   `{TOKEN VERSION}` is currently `v1` as the only option. Others may follow.
*   `{ENCODED CIPHER}` is a Base64URL encoding of the following bytes:

*   12 bytes initialization vector.
*   16 bytes authentication tag.
*   Varying number of bytes of block cipher output.

In order to decrypt a token, then, the library simply has to do the following:

1.  Decode the Base64URL-encoded string into a byte buffer.
1.  Pick bytes [0, 12] as the initialization vector.
1.  Pick bytes [12, 28] as the authentication tag.
1.  Pick bytes [28, ...] as the cipher text.
1.  Feed these to the native crypto library.

## Technical Details

A number of tehnical decisions have been made up-front to ensure there are no footguns when using the library.

Most interestingly:

### ChaCha20-Poly1305

ChaCha20-Poly1305 was chosen as the core of the library, since it offers some very appealing traits:

*   The payload is **confidential**, meaning that no one is able to learn anything about what the token grants access to.

    It wouldn't be a big problem if they did, but it's better that they don't. The token will include details like the IDs of the user the token was issued for, but it does not contain any details that could be used to elevate access rights.
*   The payload is **authentic** in the sense that the party that encrypted it held the same secret key as the party that decrypts it. Our team plays both roles in the use of this library.
*   The token is checked for **integrity**, meaning that it can not be altered by third parties without becoming invalid.

All of this is achieved with one layer of encryption, which is nice compared to the previous token library.

XChaCha-Poly1305 would have been better, but is not natively available in node. When or if it is, we could consider making a version 2 of this library.

### Compressed Payload

Deflate compression is applied to the stringified payload before encrypting it, typically resulting in worthwhile size reductions. A lot of experimentation was done with deflate, gzip, and brotli, and the results boil down to:

*   Deflate has great performance and great compression for these JSON payloads.
*   GZip has great performance and slightly worse compression for these JSON payloads.
*   Brotli has terrible performance (a factor 20 worse, often) and even better compression.

Of the three deflate seems a very good compromise between speed and compression.

[api-products]: https://gitlab.com/ds-api/api/api-products
[buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer

---
_Source: https://npm.io/package/tokien · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
