Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 67 additions & 30 deletions src/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ Authentication means knowing _who_ is doing something, either getting access or
When an Agent wants to _edit_ a resource, they have to send a signed [Commit](commits/intro.md), and the signatures are checked in order to authorize a Commit.

But how do we deal with _reading_ data, how do we know who is trying to get access?
That's what this page will explain.
The short answer is: **By signing the HTTP request**.
There are two ways users can authenticate themselves:

- Signing an `Authentication Resource` and using that as a cookie
- Opening a WebSocket, and passing an `Authentication Resource`.
- Signing every single HTTP request (more secure, less flexible)

## Design goals

Expand All @@ -17,9 +20,63 @@ The short answer is: **By signing the HTTP request**.
- **Compatible with Commits**: Atomic Commits require clients to sign things. Ideally, this functionality / strategy would also fit with the new model.
- **Fast**: Of course, authentication will always slow things down. But let's keep that to a minimum.

Authentication is done by signing individual (HTTP) requests with the Agent's private key.
## Authentication Resources

An _Authentication Resource_ is a JSON-AD object containing all the information a Server needs to make sure a valid Agent requests a session at some point in time.
These are used both in Cookie-based auth, as well as in [WebSockets](websockets.md)

We use the following fields (be sure to use the full URLs in the resource, see the example below):

- `requestedSubject`: The URL of the requested resource.
- If we're authenticating a *WebSocket*, we use the `wss` address as the `requestedSubject`. (e.g. `wss://example.com/ws`)
- If we're authenticating a *Cookie*, we use the origin of the server (e.g. `https://example.com`)
- If we're authentication a *single HTTP request*, use the same URL as the `GET` address (e.g. `https://example.com/myResource`)
- `agent`: The URL of the Agent requesting the subject and signing this Authentication Resource.
- `publicKey`: base64 serialized ED25519 public key of the agent.
- `signature`: base64 serialized ED25519 signature of the following string: `{requestedSubject} {timestamp}` (without the brackets), signed by the private key of the Agent.
- `timestamp`: Unix timestamp of when the Authentication was signed
- `validUntil` (optional): Unix timestamp of when the Authentication should be no longer valid. If not provided, the server will default to 30 seconds from the `timestamp`.

Here's what a JSON-AD Authentication Resource looks like for a WebSocket:

```json
{
"https://atomicdata.dev/properties/auth/agent": "http://example.com/agents/N32zQnZHoj1LbTaWI5CkA4eT2AaJNBPhWcNriBgy6CE=",
"https://atomicdata.dev/properties/auth/requestedSubject": "wss://example.com/ws",
"https://atomicdata.dev/properties/auth/publicKey": "N32zQnZHoj1LbTaWI5CkA4eT2AaJNBPhWcNriBgy6CE=",
"https://atomicdata.dev/properties/auth/timestamp": 1661757470002,
"https://atomicdata.dev/properties/auth/signature": "19Ce38zFu0E37kXWn8xGEAaeRyeP6EK0S2bt03s36gRrWxLiBbuyxX3LU9qg68pvZTzY3/P3Pgxr6VrOEvYAAQ=="
}
```

## Atomic Cookies Authentication

In this approach, the client creates and signs a Resource that proves that an Agent wants to access a certain server for some amount of time.
This Authentication Resource is stored as a cookie, and passed along in every HTTP request to the server.

### Setting the cookie

## HTTP Headers
1. Create a signed Authentication object, as described above.
2. Serialize it as JSON-AD, then as a base64 string.
3. Store it in a Cookie
1. It needs to be
2. The expiration date of the cookie should be set, and should match the expiration date of the Authentication Resource.
3. Set the `Secure` attribute to prevent Man-in-the-middle attacks over HTTP

## Authenticating Websockets

After [opening a WebSocket connection](websockets.md), create an Authentication Resource.
Send a message like so: `AUTHENTICATE {authenticationResource}`.
The server will only respond if there is something wrong.

## Per-Request Signing

Atomic Data allows **signing every HTTP request**.
This method is most secure, since a MITM attack would only give access to the specific resource requested, and only for a short amount of time.
Note that signing every single request takes a bit of time.
We picked a fast algorithm (Ed25519) to minimize this cost.

### HTTP Headers

All of the following headers are required, if you need authentication.

Expand All @@ -28,7 +85,7 @@ All of the following headers are required, if you need authentication.
- `x-atomic-timestamp`: The current time (when sending the request) as milliseconds since unix epoch
- `x-atomic-agent`: The subject URL of the Agent sending the request.

## Sending a request
### Sending a request

Here's an example (js) client side implementation with comments:

Expand All @@ -52,33 +109,14 @@ headers.set('x-atomic-agent', agent?.subject);
const response = await fetch(subject, {headers});
```

## Handling a request
## Verifying an Authentication

- If none of the `x-atomic` HTTP headers are present, the server assigns the [PublicAgent](https://atomicdata.dev/agents/publicAgent) to the request. This Agent represents any guest who is not signed in.
- If some (but not all) of the `x-atomic` headers are present, the server will return with a `500`.
- The server must check the timestamp (max 10 seconds difference).
- The server must check if the `validUntil` has not yet passed.
- The server must check whether the public key matches the one from the Agent.
- The server must check if the signature is valid.
- The server must check if the request resource can be shared

## Authentication Resources (used in [websockets](websockets.md))

An _Authentication Resource_ is a JSON-AD object containing all the information a Server needs to make sure a valid Agent requests a session at some point in time.
It's fields are very similar to the headers shown above.
The signature still is a base64 signature of the following string: `{requestedSubject} {timestamp}`.
In Websocket connections, we use the `wss` address as the `requestedSubject`.

```json
{
"https://atomicdata.dev/properties/auth/agent": "http://example.com/agents/N32zQnZHoj1LbTaWI5CkA4eT2AaJNBPhWcNriBgy6CE=",
"https://atomicdata.dev/properties/auth/requestedSubject": "wss://example.com/ws",
"https://atomicdata.dev/properties/auth/publicKey": "N32zQnZHoj1LbTaWI5CkA4eT2AaJNBPhWcNriBgy6CE=",
"https://atomicdata.dev/properties/auth/timestamp": 1661757470002,
"https://atomicdata.dev/properties/auth/signature": "19Ce38zFu0E37kXWn8xGEAaeRyeP6EK0S2bt03s36gRrWxLiBbuyxX3LU9qg68pvZTzY3/P3Pgxr6VrOEvYAAQ=="
}
```

The Authentication Resource is sent after the websocket is opened, like so: `AUTHENTICATE {authenticationResource}`.
- The server should check if the request resource can be accessed by the Agent using [hierarchy](hierarchy.md) (e.g. check `read` right in the resource or its parents).

## Hierarchies for authorization

Expand All @@ -87,6 +125,5 @@ Atomic Data uses [Hierarchies](hierarchy.md) to describe who gets to access some
## Limitations / considerations

- Since we need the Private Key to sign Commits and requests, the client should have this available. This means the client software as well as the user should deal with key management, and that can be a security risk in some contexts (such as a web browser). [See issue #49](https://github.com/ontola/atomic-data-docs/issues/49).
- When using the Agent's subject to authenticate somwehere, the authorizer must be able to check what the public key of the agent is. This means the agent must be publicly resolvable. This is one of the reasons we should work towards a server-independent identifier, probably as base64 string that contains the public key (and, optionally, also the https identifier). See [issue #59 on DIDs](https://github.com/ontola/atomic-data-docs/issues/59).
- Signing every single request takes a bit of time. We picked a fast algorithm (Ed25519) to minimize this cost.
- We'll probably introduce some form of token-based-authentication in the future. [See #87](https://github.com/ontola/atomic-data-docs/issues/87)
- When using the Agent's subject to authenticate somewehere, the authorizer must be able to check what the public key of the agent is. This means the agent must be publicly resolvable. This is one of the reasons we should work towards a server-independent identifier, probably as base64 string that contains the public key (and, optionally, also the https identifier). See [issue #59 on DIDs](https://github.com/ontola/atomic-data-docs/issues/59).
- We'll probably also introduce some form of token-based-authentication created server side in the future. [See #87](https://github.com/ontola/atomic-data-docs/issues/87)
8 changes: 6 additions & 2 deletions src/websockets.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ The `WebSocket-Protocol` is `AtomicData`.
- `SUBSCRIBE ${subject}` tells the Server that you'd like to receive Commits about this Subject.
- `UNSUBSCRIBE ${subject}` tells the Server that you'd like to stop receiving Commits about this Subject.
- `GET ${subject}` fetch an individual resource.
- `AUTHENTICATE ${authenticationResource}` to set a user session for this websocket and allow authorized requests. The `authenticationResource` is a JSON-AD resource containing the signature and more, see [Authentication](../src/authentication.md). No response is given if the request is valid, an error is returned if something is wrong.
- `AUTHENTICATE ${authenticationResource}` to set a user session for this websocket and allow authorized messages. The `authenticationResource` is a JSON-AD resource containing the signature and more, see [Authentication](../src/authentication.md).

## Server to client messages

- `COMMIT ${CommitBody}` an entire [Commit](../src/commits/concepts.md) for a resource that you're subscribed to.
- `RESOURCE ${Resource}` a JSON-AD Resource as a response to a GET request. If there is something wrong with this request (e.g. 404), return a `Error` Resource with the requested subject, similar to how the HTTP protocol server does this.`
- `RESOURCE ${Resource}` a JSON-AD Resource as a response to a `GET` message. If there is something wrong with this request (e.g. 404), return a `Error` Resource with the requested subject, similar to how the HTTP protocol server does this.`
- `ERROR ${ErrorBody}` an Error resource is sent whenever something goes wrong. The `ErrorBody` is a plaintext, typically English description of what went wrong.

## Considerations

- For many messages, there is no response to give if things are processed correctly. If a message is unknown or there is a different problem, return an `ERROR`.

## Example implementations

- [Example client implementation in Typescript (@tomic/lib).](https://github.com/atomicdata-dev/atomic-data-browser/blob/main/lib/src/websockets.ts)
Expand Down