# Introduction

OAuth uses three types of "tokens", i.e. _authorization code_, 
_refresh token_ and _access token_. Each has their own characteristics. The 
_authorization code_ is used during _authorization_, i.e. the OAuth client will 
obtain it through a browser query parameter. This _authorization code_ is then
exchanged by the client for a _refresh token_ and _access token_.

| "Token"              | Single Use | Default Lifetime |
| -------------------- | ---------- | ---------------- |
| _authorization code_ | Yes        | 5 minutes        |
| _refresh_token_      | Yes        | 90 days          |
| _access_token_       | No         | 1 hour           | 

If ever an _authorization code_ or _refresh token_ is reused, the entire 
"authorization" must be revoked, i.e. after this they should all be invalidated
and the client needs to again obtain authorization.

Each of these tokens has some associated information, e.g. the client 
identifier that was used, the user that authorized the client, the "scope" that 
indicates which functionality the client was approved for by the user.

There's three approaches one can take dealing with tokens: 

1. use random strings as tokens and link them to the required information by
   using a database;
2. use digital signatures over the tokens where the tokens themselves contain 
   the information.
3. use a hybrid approach where some information is stored in the database, but 
   the remainder in the token itself.

Technically option 2 is not possible, as there needs to be a revocation 
mechanism and 'replay' needs to be detected as well. This leaves the other two
options.

Option 3 has some advantages:

1. Limit the amount of data stored in the database, not every _access token_ 
   needs an entry in the database;
2. By verifying signatures first, the database is not hit at all for invalid 
   tokens;
3. Allow "third party" _access token_ verification when revocation is not 
   necessary for a short lived _access token_ for the duration of 1 hour.

The remainder of this document explains the signed token format. The explicit 
goals of this token format are:

1. No [JSON parsing](https://seriot.ch/projects/parsing_json.html) required 
   before verifying the signatures in order to reduce attack surface;
2. Key IDs MUST be "first class citizens" in order to link tokens to particular
   keys to support _key rollover_ or third party signature verification;
3. Keys MUST be bound to the tokens it signs;
4. Not only the tokens, but also the keys MUST have a version;
5. No _algorithm_ agility, but token/key version agility.

Some of these lessons were learned from experimenting with 
[JWT](https://datatracker.ietf.org/doc/html/rfc7519) and 
[PASETO](https://paseto.io/).

# Format

## Key Format

We have both a secret key (`SECRET_KEY`) and a public key (`PUBLIC_KEY`). The 
[EdDSA](https://en.wikipedia.org/wiki/EdDSA) algorithm is used with Curve25519. 
The public key is 32 bytes. The secret key is the 64 bytes, 32 bytes secret 
component and 32 bytes public component.

The `SECRET_KEY` and `PUBLIC_KEY` below are Base64 URL-safe encoded without 
padding, see section 5 of 
[RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648#section-5).

Applications MUST store the secret and public keys in the format below. The key
structure is verified when using the signer to avoid key confusion.

### Key ID

The `KEY_ID` is 16 _characters_ long. The `KEY_ID` is randomly generated when 
the key is created and stored as part of the key. The characters of the 
`KEY_ID` MUST be part of the Base64 URL-safe alphabet. The easiest way to 
generate a `KEY_ID` is generate 12 random bytes and encode them using Base64 
URL-safe encoding, this always results in a string of 16 characters.

### Secret Key

```
k7.sec.KEY_ID.SECRET_KEY
```

Example:

```
k7.sec.kCLRNiq5rDNbnjZs.lFiA-paoVwkYIALTgcxqtEGGnAk7XiOWSldM-ITD2segO1S6jlOPe6uDBuH4gVJvt6-psMVSpyTMcsOOiTRqqg
```

### Public Key

```
k7.pub.KEY_ID.PUBLIC_KEY
```

Example:

```
k7.pub.kCLRNiq5rDNbnjZs.oDtUuo5Tj3urgwbh-IFSb7evqbDFUqckzHLDjok0aqo
```

This example public key belongs to the secret key show above.

## Token Format

The `KEY_ID`, `SIGNED_DATA` and `SIGNATURE` below are Base64 URL-safe encoded 
without padding.

```
v7.KEY_ID.SIGNED_DATA.SIGNATURE
```

The Key ID of the key used to sign the token is embedded in the token just as 
with the key.

The signature is calculated over `v7.KEY_ID.SIGNED_DATA`, where `KEY_ID` and 
`SIGNED_DATA` are already Base64 URL-safe encoded.

Assuming the `SIGNED_DATA` is `Hello World!`, which is Base64 URL-safe encoded 
`SGVsbG8gV29ybGQh`, the signature is calculated over the string 
`v7.kCLRNiq5rDNbnjZs.SGVsbG8gV29ybGQh`.

Example:

```
v7.kCLRNiq5rDNbnjZs.SGVsbG8gV29ybGQh.kX_bwkhOKPJj-BUXSaWe42taKGoy5mKyq38rIYbl5xv2DvxFszR1Z6pCGZSM_ooKlY2Z-gQBMv3fCmCHCEdCCg
```

When verifying `v7` tokens the (locally) available keys MUST have the `k7` 
format. The Key ID in the local key MUST match the one in the token.
