Skip to content

Commit

Permalink
#10: Documentation of JWT authentication & signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
gsvarovsky committed Nov 21, 2023
1 parent 39e21c2 commit ed0eec9
Show file tree
Hide file tree
Showing 13 changed files with 379 additions and 24 deletions.
12 changes: 12 additions & 0 deletions doc/_includes/http/named-subdomains/put-with-signer.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
PUT {{origin}}/api/v1/domain/{{account}}/{{subdomain}}
Accept: application/json
Authorization: Basic {{digest}}
Content-Type: application/json

{
"useSignatures": true,
"user": {
"@id": "≪user URI≫",
"key": { "keyid": "≪keyid≫", "public": "≪base64(DER encoded RSA public key)≫" }
}
}
1 change: 1 addition & 0 deletions doc/_includes/sidebar.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
</p>
<ul class="menu-list">
<li><a href="self-host">Self-Hosting</a></li>
<li><a href="signed-updates">Signatures</a></li>
<li>
<a href="https://github.com/m-ld/m-ld-gateway/tree/v{{ '{{ version }}' }}/architecture">Architecture</a>
</li>
Expand Down
28 changes: 24 additions & 4 deletions doc/accounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,37 @@ The Gateway root account can be used to create any user account directly.

The body of the response will be of the form `{ "auth": { "key": "≪my-key≫" } }`, where `≪my-key≫` is the new account's authorisation key.

## setting remotes authentication options
## remotes authentication options

When [connecting to subdomains](clone-subdomain), clients may need to provide authentication. The following options are available:
- `anon` allows the client not to include any authentication
- `key` requires the client to know and provide the account key (the default)
- `jwt` requires the client to provide a JWT signed by the account key
- `anon` allows the client not to include any authentication (this can only be used with [UUID subdomains](uuid-subdomains))
- `jwt` requires the client to provide a JWT signed by the account key (see below)

The required option can be set as follows.

```
{% include 'http/accounts/insert-remotes-auth.http' %}
```

You can also remove a previously-set option by including a delete clause, for example: `{ "@delete": { "remotesAuth": "jwt" } }`.
You can also remove a previously-set option by including a delete clause, for example: `{ "@delete": { "remotesAuth": "jwt" } }`.

### using JWT authentication tokens

The `key` authentication option requires the client to have access to the account key. If the current user is the account owner and their code is running in a secure environment, for example under an operating system user account, this may be fine. The `jwt` option allows you to provision short-lived tokens for use in insecure environments, or by users who are not the account owner.

When `jwt` authentication is available, the end-point to [create a named subdomain](named-subdomains) responds with a configuration containing a newly-minted token, valid for 10 minutes.

A typical app design is shown below.

![JWT login sequence diagram](img/jwt-login.seq.svg)

1. The App's Client (e.g. a browser page) performs the necessary steps to log into the app. This is independent of the Gateway and **m-ld**.
2. The app's identity provider will issue some kind of credential, usually itself a token.
3. In order to obtain the necessary configuration and Gateway JWT for cloning the domain, the client needs to exchange the app token for a Gateway token. It does this by calling a serverless lambda (this could also be a straightforward service).
4. The app lambda is able to validate the client's app token according to the rules of the identity provider. It now knows that the user is identified and logged in.
5. The app lambda can also check that the user does in indeed have access to the domain according to the app's access control rules. (This could involve inspection of the app token, and/or another call to the identity provider, if it also supports authorisation.)
6. The app lambda uses its Gateway account key (typically available via an environment variable) to [idempotently PUT the subdomain](named-subdomains#creating-a-named-domain) to the Gateway. If desired, the user identity can be be indicated using a JSON request body of the form `{"user": {"@id": "≪URI≫"}}`, and the identity will be included as the `sub` (subject) of the returned JWT.
7. The Gateway responds with the necessary configuration and a newly minted JWT.
8. The app lambda responds the configuration to the client.
9. The client is now able to connect to the domain using the provided configuration, including its JWT.
43 changes: 26 additions & 17 deletions doc/clone-subdomain.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,44 @@ title: getting started
---
# Cloning a Subdomain

To connect a new clone to a Gateway subdomain, use the configuration provided in the domain creation response. For Socket.io messaging (the default), the configuration will look like this:
To connect a new clone to a Gateway subdomain, use the configuration provided in the domain creation response. For Socket.io messaging (the default), the configuration will look something like this:

```json
{
"@domain": "{{ subdomain }}.{{ account }}.{{ '{{ domain }}' }}",
"genesis": false,
"io": {
"uri": "{{ '{{ origin }}' }}",
"opts": {
"auth": {
"user": "{{ account }}",
"key": "{{ accountKey }}"
}
}
"opts": {}
}
}
```

(Note that for [UUID subdomains](uuid-subdomains), the `genesis` flag will be missing, as the Gateway does not know whether the subdomain already exists.)
For a [UUID subdomain](uuid-subdomains):
- The `opts` object will be missing (no authorisation information is needed).
- The `genesis` flag will be missing, as the Gateway does not know whether the subdomain already exists. If the domain does not already exist according to your app, set the `genesis` flag to `true`.

For a [named subdomain](named-subdomains), the `opts` object will take one of the following two forms:
- If the account allows [JWT authentication](accounts#remotes-authentication-options), a JWT will be provided, signed by the account:
```json
{
"auth": {
"jwt": "≪jwt≫"
}
}
```
- Otherwise, the account will be echoed, with a placeholder for the account key:
```json
{
"auth": {
"user": "{{ account }}",
"key": "{{ accountKey }}"
}
}
```
It's up to the app to replace the `"key"` placeholder with the account key (it's always a placeholder, even if you used the account key to create the subdomain).

To use this config, augment it as follows:
1. Add an `"@id"` key with a unique clone identifier.
2. Replace the `"key"` placeholder (if it exists; it's always a placeholder, even if you used the account key to create the subdomain).
3. For UUID subdomains, if the domain does not already exist, set the `genesis` flag to `true`.
Finally, prior to using the config, you need to an `"@id"` key with a unique clone identifier for any new clone you create.

For example, using the [JavaScript engine](https://js.m-ld.org/):

Expand All @@ -38,11 +52,6 @@ import { MemoryLevel } from 'memory-level';

function startClone(config) {
config['@id'] = uuid();
config.io.opts.auth.key = 'my-key';
return clone(new MemoryLevel(), IoRemotes, config);
}
```

You will note that this requires the client to have access to the account key. If the current user is the account owner and their code is running in a secure environment, for example an operating system with a user login, this may be fine. Otherwise, it would be better to derive a user login token with restricted lifetime from the account key.

> 🚧 More detail on user tokens will be available here soon.
68 changes: 68 additions & 0 deletions doc/img/jwt-login.seq.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ed0eec9

Please sign in to comment.