-
Notifications
You must be signed in to change notification settings - Fork 586
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Edit NIP-42 instead of creating a new NIP
- Loading branch information
1 parent
22760a4
commit cfbf9da
Showing
6 changed files
with
113 additions
and
183 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,90 +1,101 @@ | ||
> __Warning__ `unrecommended`: deprecated in favor of [NIP-43](43.md) | ||
NIP-42 | ||
====== | ||
|
||
Authentication of clients to relays | ||
----------------------------------- | ||
|
||
`draft` `optional` `author:Semisol` `author:fiatjaf` | ||
`draft` `optional` `author:Semisol` `author:fiatjaf` `author:arthurfranca` | ||
|
||
This is a `client` to `relay` authentication procedure which is | ||
fast and easy to implement because it's non-interactive. | ||
|
||
This NIP defines a way for clients to authenticate to relays by signing an ephemeral event. | ||
Authentication happens on connection start, not | ||
requiring message exchange between `client` and `relay`. | ||
|
||
## Motivation | ||
|
||
A relay may want to require clients to authenticate to access restricted resources. For example, | ||
|
||
- A relay may request payment or other forms of whitelisting to publish events -- this can naïvely be achieved by limiting publication | ||
to events signed by the whitelisted key, but with this NIP they may choose to accept any events as long as they are published from an | ||
authenticated user; | ||
- A relay may limit access to `kind: 4` DMs to only the parties involved in the chat exchange, and for that it may require authentication | ||
before clients can query for that kind. | ||
- A relay may limit subscriptions of any kind to paying users or users whitelisted through any other means, and require authentication. | ||
- A relay may request payment or other forms of whitelisting to publish events. | ||
It may choose to accept events with any pubkey value as long as they are published from an authenticated user; | ||
- A relay may limit access to `kind: 4` DMs to only the parties involved in the chat exchange, | ||
and for that it may require authentication before clients can query for that kind. | ||
- A relay may limit subscriptions of any kind to paying users or users whitelisted through any other means, | ||
and require authentication. | ||
|
||
## How to Authenticate | ||
|
||
The `client` must generate a `kind: 22242` ephemeral event with the current time as `created_at` | ||
and the relay url as a `relay` tag. | ||
Then it has to stringify the JSON event and [percent-encode](https://www.rfc-editor.org/rfc/rfc3986#page-12) it. | ||
The resulting string is used as `authorization` query param when connecting to the relay. | ||
|
||
### Javascript Example | ||
|
||
```js | ||
const relayUrl = 'wss://relay.example.com' | ||
// add id and signature as usual | ||
const jsonEvent = generateNostrEvent({ | ||
pubkey: "...", | ||
created_at: Math.floor(Date.now() / 1000), | ||
kind: 22242, | ||
tags: [['relay', relayUrl]], | ||
content: "" | ||
}) | ||
const auth = window.encodeURIComponent(JSON.stringify(jsonEvent)) | ||
const ws = new WebSocket(`${relayUrl}?authorization=${auth}`) | ||
ws.addEventListener('open', () => console.log('auth accepted')) | ||
ws.addEventListener('close', () => console.log('disconnected') ) | ||
``` | ||
|
||
## Definitions | ||
## Relay Handling | ||
|
||
This NIP defines a new message, `AUTH`, which relays can send when they support authentication and clients can send to relays when they want | ||
to authenticate. When sent by relays, the message is of the following form: | ||
`Relays` that require users to authenticate before performing atleast one action, must authenticate user on connection request. | ||
The authorization query param must be decoded and validated. A valid event must: | ||
- be of `kind` `22242`; | ||
- have `created_at` within a small time window relative to the current date (e.g. 60 seconds); | ||
- have the `relay` tag url value with the same domain name as the relay. | ||
|
||
``` | ||
["AUTH", <challenge-string>] | ||
``` | ||
If the event is valid, its `pubkey` identifies the authenticated user. | ||
|
||
And, when sent by clients, of the following form: | ||
The `relay` may end the socket right away if the user is not authorized to access the `relay`. | ||
|
||
``` | ||
["AUTH", <signed-event-json>] | ||
``` | ||
The `relay` should send a `NOTICE` or `OK` message (as per [NIP-20](20.md)) with a standard prefix `"restricted: "` (readable both by humans and machines) | ||
when blocking authenticated user from accessing some resources. | ||
|
||
The signed event is an ephemeral event not meant to be published or queried, it must be of `kind: 22242` and it should have at least two tags, | ||
one for the relay URL and one for the challenge string as received from the relay. | ||
Relays MUST exclude `kind: 22242` events from being broadcasted to any client. | ||
`created_at` should be the current time. Example: | ||
|
||
```json | ||
{ | ||
"id": "...", | ||
"pubkey": "...", | ||
"created_at": 1669695536, | ||
"kind": 22242, | ||
"tags": [ | ||
["relay", "wss://relay.example.com/"], | ||
["challenge", "challengestringhere"] | ||
], | ||
"content": "", | ||
"sig": "..." | ||
} | ||
``` | ||
For example, if an already authenticated user A requests DMs not sent by him and meant to be read only by user B, | ||
relay should send a `NOTICE` like this one: | ||
|
||
## Protocol flow | ||
``` | ||
["NOTICE", "restricted: not authorized to access this user's DMs, please login with NIP-42"] | ||
``` | ||
|
||
At any moment the relay may send an `AUTH` message to the client containing a challenge. After receiving that the client may decide to | ||
authenticate itself or not. The challenge is expected to be valid for the duration of the connection or until a next challenge is sent by | ||
the relay. | ||
## Security Measures | ||
|
||
The client may send an auth message right before performing an action for which it knows authentication will be required -- for example, right | ||
before requesting `kind: 4` chat messages --, or it may do right on connection start or at some other moment it deems best. The authentication | ||
is expected to last for the duration of the WebSocket connection. | ||
The used protocol must be `wss` (WebSocket Secure). | ||
|
||
Upon receiving a message from an unauthenticated user it can't fulfill without authentication, a relay may choose to notify the client. For | ||
that it can use a `NOTICE` or `OK` message with a standard prefix `"restricted: "` that is readable both by humans and machines, for example: | ||
The authorization event `id` should be stored by the `relay` for the same above mentioned time window so that | ||
if the same event `id` is used twice, the `relay` should reject the connection immediately and | ||
should also disconnect the user that first used the event to authenticate. | ||
In this case the `relay` may send a `NOTICE` message with `"restricted: "` prefix informing | ||
the first user before disconnecting him, for example: | ||
|
||
``` | ||
["NOTICE", "restricted: we can't serve DMs to unauthenticated users, does your client implement NIP-42?"] | ||
["NOTICE", "restricted: your client is misbehaving, authorization event can't be reused."] | ||
``` | ||
|
||
or it can return an `OK` message noting the reason an event was not written using the same prefix: | ||
## Backward Compatibility | ||
|
||
`Relays` may wish to support the legacy flow (it was in effect from 16/jan/2023 to 1/jun/2023) if the `authorization` query param is absent. They should send to connecting `clients`, at any moment, an `AUTH` message containing a `challenge`, as follows: | ||
|
||
``` | ||
["OK", <event-id>, false, "restricted: we do not accept events from unauthenticated users, please sign up at https://example.com/"] | ||
["AUTH", <challenge-string>] | ||
``` | ||
|
||
## Signed Event Verification | ||
`Clients` may then reply with an `AUTH` message containg a `kind` `22242` event, similar to the above mentioned one, **with** the same challenge as a tag, for example: | ||
|
||
To verify `AUTH` messages, relays must ensure: | ||
``` | ||
["AUTH", { "kind": 22242, ..., "tags": [["relay", "wss://relay.example.com/"], ["challenge", "challengestringhere"]] }] | ||
``` | ||
|
||
- that the `kind` is `22242`; | ||
- that the event `created_at` is close (e.g. within ~10 minutes) of the current time; | ||
- that the `"challenge"` tag matches the challenge sent before; | ||
- that the `"relay"` tag matches the relay URL: | ||
- URL normalization techniques can be applied. For most cases just checking if the domain name is correct should be enough. | ||
`Relays` should validate the event as mentioned above, additionally checking if the `challenge` match the previously sent one. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.