Skip to content

NIP-42 AUTH

khatru supports NIP-42 out of the box. The functionality is exposed in the following ways.

Sending arbitrary AUTH challenges

At any time you can send an AUTH message to a client that is making a request.

It makes sense to give the user the option to authenticate right after they establish a connection, for example, when you have a relay that works differently depending on whether the user is authenticated or not.

go
relay := khatru.NewRelay()

relay.OnConnect = append(relay.OnConnect, func(ctx context.Context) {
	khatru.RequestAuth(ctx)
})

This will send a NIP-42 AUTH challenge message to the client so it will have the option to authenticate itself whenever it wants to.

Signaling to the client that a specific query requires an authenticated user

If on RejectFilter or RejectEvent you prefix the message with auth-required: , that will automatically send an AUTH message before a CLOSED or OK with that prefix, such that the client will immediately be able to know it must authenticate to proceed and will already have the challenge required for that, so they can immediately replay the request.

go
relay.RejectFilter = append(relay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) {
	return true, "auth-required: this query requires you to be authenticated"
})
relay.RejectEvent = append(relay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) {
	return true, "auth-required: publishing this event requires authentication"
})

Reading the auth status of a client

After a client is authenticated and opens a new subscription with REQ or sends a new event with EVENT, you'll be able to read the public key they're authenticated with.

go
relay.RejectFilter = append(relay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) {
	authenticatedUser := khatru.GetAuthed(ctx)
})

Telling an authenticated user they're still not allowed to do something

If the user is authenticated but still not allowed (because some specific filters or events are only accessible to some specific users) you can reply on RejectFilter or RejectEvent with a message prefixed with "restricted: " to make that clear to clients.

go
relay.RejectFilter = append(relay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) {
	authenticatedUser := khatru.GetAuthed(ctx)

	if slices.Contain(authorizedUsers, authenticatedUser) {
		return false
	} else {
		return true, "restricted: you're not a member of the privileged group that can read that stuff"
	}
})

Reacting to a successful authentication

Each khatru.WebSocket object has an .Authed channel that is closed whenever that connection performs a successful authentication.

You can use that to emulate a listener for these events in case you want to keep track of who is authenticating in real time and not only check it when they request for something.

go
	relay.OnConnect = append(relay.OnConnect,
		khatru.RequestAuth,
		func(ctx context.Context) {
			go func(ctx context.Context) {
				conn := khatru.GetConnection(ctx)
				select {
				case <-ctx.Done():
					fmt.Println("connection closed")
				case <-conn.Authed:
					fmt.Println("authenticated as", conn.AuthedPublicKey)
				}
			}(ctx)
		},
	)
~