-
Notifications
You must be signed in to change notification settings - Fork 586
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NIP-98 HTTP Auth #469
NIP-98 HTTP Auth #469
Conversation
This comment was marked as spam.
This comment was marked as spam.
How is the event delivered to the API? Is it published to a relay? I would be concerned about replay attacks, this doesn't seem secure. I don't understand why a direct request wouldn't work, is this for delegating authentication? |
Idempotency can be managed by the API and is not a concern for this spec, timestamp checks should be good enough in most cases. |
Ah, ok, I missed that. LGTM I guess then. This is a criticism of zaps as well, but it's strange to use the standard event structure but then encode it and throw it in some other place like a query string or authentication header rather than just using a regular HMAC or signing the url you're requesting directly or whatnot. Putting it all in a valid event makes me worry that a confused client will publish this event, which contains sensitive information, to relays, which is possible and definitely not what you want to do. |
The main reason for using nostr events is that we already have infrastructure to create signed payloads via NIP-7. Suggesting something like regular HMAC auth would require access to private keys or changes to NIP-7, we have a JSON object which is similar to JWT here and i see an opportunity to re-use the nostr identity in external systems over regular HTTP requests.
|
Is this the spec we want for all "Nostr login" use cases? |
@Dolu89 @lukechilds can you take a look at this? |
It's interesting reading this proposal, coming from some slight use of JWTs with shared secrets on servers. Clearly for Nostr we want to auth users asymmetrically. It is technically possible for Nostr clients to generate their own JWT for id. To validate permissions to a resource, an organization would have to run their own auth service, use Nostr to validate id, but add claims to the JWT. Wrapping the auth+httprequest in a Nostr event adds extra overhead. Clearly it works, but figuring out auth via the existing JWT methods will allow for more services to use Nostr auth without customization. |
Nice NIP but I wonder why here there is no On NIP-42 I suggested an authorization on connection with something like |
I just implemented it in nostrcheck-api-ts it seems to me a very elegant solution to authenticate a request to a public API. Thanks @v0l 🙂 |
Have we considered including notes for common HTTP request failure responses? Effectively returning For either of those responses, we could perhaps define a As an example:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate |
I wonder the same, @arthurfranca. |
This comment was marked as spam.
This comment was marked as spam.
@melvincarvalho do you understand why it is absolutely necessary for NIP-42 and not for this? I'm asking honestly because I forgot. For NIP-42 I think it wasn't just providing "extra security", it was absolutely necessary. |
@fiatjaf as an example of prior work that includes a challenge, the lightning lud-04 spec includes a k value. Effectively making it an interactive protocol. I just watched the Fedimint schnorr signature tech talk, and they mentioned it's possible to do this non-interactively where you effectively use a zero-knowledge proof (using a provably random number as I understand it) instead of a challenge/interactive protocol. I'm waiting for the slides to be posted, as I don't have a full description of how it functions. For protocol based NIPs, if may be useful to include a diagram to make it clear. Something like this perhaps.
|
The challenge is to avoid replay attacks. In NIP-42 an impostor can send an event created by someone else and the relay will believe the impostor is that someone else. In this NIP-98, an imposter can do the same thing (within the suggested 60 seconds). Under normal usage impostors won't have access to the authenticating event. But in case they do through some other security weakness, it should only be usable once. This is for security-in-depth. If an interactive protocol is intolerable, then we should be looking at zk-SNARK (although I'm not sure it provides the no-replay freshness guarantee we are looking for) |
I think I disagree here. Most authentication services have this problem. If you leak your JWT your have a problem until it expires. 60 seconds is much shorter than most token expiries. And if most people consider this a problem, servers could store the id of the event, similar to how oath keeps track of used tokens, and forbid reuse. It would mean that the client will have to rebuild events for every subsequent request though, which sounds undesirable |
How? If the user Bob sends his event directly to the relay Ronald, and the event has a commitment to the relay URL, what can Ronald do with that? It can't use that to auth to other relays. |
As long as the websocket is wss, I can't see much difference from an
enhanced API key with an expiry. And if the signed event exposed, it almost
certainly a client mishandling folly - which any protocol would suffer
from. Another risk is DNS hijacking.
From oauth docs: "state (recommended): The state parameter is used by the
application to store request-specific data and/or prevent CSRF attacks. The
authorization server must return the unmodified state value back to the
application."
REF:
https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/
I agree that a relay could store the event_id as state for extra
protection, and reject it if received again. There is still a race
condition between event signing and the relay receiving the event - if a
malicious actor could be first to publish.
I'd prefer if we can end up with a non-interactive auth spec - and I think
it's entirely possible.. just unsure the added complexity. Pain points are
an extra round trip, and for things like APIs, you need to somehow get a
challenge code before every API request.
Another thing regarding performance, due to non-zero event signing cost, it
may be nice to allow multiple u tags, with multiple methods, and perhaps
flatten everything.
[
["u", "http://website.com/API/happy", ["GET", "POST"]],
["u", "http://website.com/API/sad", ["DELETE"]]
].
A good example case for above is a paywall CDN, where you can sign an event
once, and then fetch multiple images/media using the same auth request -
which would be normal behaviour during a webpage load.
REF: I'm using this NIP in
https://github.com/blakejakopovic/nostr_paywall_example/tree/master, and
will migrate NostrGraph login to using this NIP when it's ready.
On 11 May 2023 at 12:17:10 pm, fiatjaf_ ***@***.***) wrote:
The challenge is to avoid replay attacks. In NIP-42 an impostor can send an
event created by someone else and the relay will believe the impostor is
that someone else.
How? If the user Bob sends his event directly to the relay Ronald, and the
event has a commitment to the relay URL, what can Ronald do with that? It
can't use that to auth to other relays.
—
Reply to this email directly, view it on GitHub
<#469 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AADILOTW6M7FA4MTYHZNIA3XFSVBNANCNFSM6AAAAAAXJKU2PI>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Ronald being the relay can't do anything interesting. But any 3rd party getting ahold of it could trick Ronald. |
Should we change NIP-42 to remove the challenge? @mikedilger That would make it much simpler. |
Same as NIP-42.
Same as NIP-42. (It is suggested 10 min but it could also be 60 seconds)
Relays (NIP-42 use-case) have even lower risk cause not only the auth event is signed, but all other payloads are signed events that a malicious client wouldn't be able to create without a nsec
Totally agree! Also for NIP-42. NIP-98 main difference is that on every request, a new auth event is created. To be clear, I'm in favor of not using a challenge for both NIPs. NIP-42 should be as easy as |
No. I'm not following your gist here. NIP-42 and NIP-98 need challenges. The challenge in NIP-42 is needed. The attack is like this:
Even with AUTH (but without wss) there is a session-hijacking attack where Eve can hijack the session and Ronald thinks he is continuing to talk with Bob, but it is now Eve. So NIP-42 should require WSS, not just WS. |
This comment was marked as spam.
This comment was marked as spam.
I agree that WSS prevents session hijacking. I'm no fan of trusting in "short-lived". Eve can attack microseconds after. Anything the legitimate user could do, the attacker could do. A nonce could work, but that means the server has to remember every nonce. If you think that is easier than the server issuing a challenge, then ok. But they have to be tracked forever, kind of like an ever growing blockchain. I should presume bitcoiners are fine with that craziness. I hear you on idempotent operations. Replaying such is not a risk if they truly are fully idempotent. IP binding isn't secure. Kevin Mitnick attacked Tsusomu Shimumora's lab using half-open TCP connections that spoofed the IP address... way back when I was learning computer security in 1994 or so. |
It would only have to be kept until the timestamp goes out of the allowed timeframe |
The nonce is the event id. Just need to keep it around for the allowed time window (60 seconds).
For using the same ip on an http connection I only imagine it possible if using the same local network (like same lan house) or hacking the user ISP, but I'm not Kevin Mitnick xD. Atleast it is an extra security layer. Regarding a NIP-42 version without challenge (NIP-98 doesn't need it as it is not a persistent session - just keep track of event id), So NIP-42 is overkill and has poor experience for the current use-cases (rate-limiting and [paid] relay login). |
4. The `method` tag MUST be the same HTTP method used for the requested resource. | ||
|
||
When the request contains a body (as in POST/PUT/PATCH methods) clients SHOULD include a SHA256 hash of the request body in a `payload` tag as hex (`["payload", "<sha256-hex>"]`), servers MAY check this to validate that the requested payload is authorized. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When calling an [unsafe](https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP) http method (all but GET, HEAD and OPTIONS), server SHOULD keep track of the event id for the above time window duration to prevent reuse. | |
``` | ||
Authorization: Nostr eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIzMTEwYmUzYTU3ZGZlNGI1Nzg3MzQiLCJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRkMDllMThiZmQ3NmFhYmMyNGEwMDgxY2UyODU2ZjMzMDUwNGVkIiwiY29udGVudCI6IiIsImtpbmQiOjI3MjM1LCJjcmVhdGVkX2F0IjoxNjgyMzI3ODUyLCJ0YWdzIjpbWyJ1cmwiLCJodHRwczovL2FwaS5zbm9ydC5zb2NpYWwvYXBpL3YxL241c3AvbGlzdCJdLFsibWV0aG9kIiwiR0VUIl1dLCJzaWciOiI1ZWQ5ZDhlYzk1OGJjODU0Zjk5N2JkYzI0YWMzMzdkMDA1YWYzNzIzMjQ3NDdlZmU0YTAwZTI0ZjRjMzA0MzdmZjRkZDgzMDg2ODRiZWQ0NjdkOWQ2YmUzZTVhNTE3YmI0M2IxNzMyY2M3ZDMzOTQ5YTNhYWY4NjcwNWMyMjE4NCJ9 | ||
``` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replay Attacks
While this NIP assures that the kind 27235
event guarantees that its author authorizes using the stated HTTP method at the stated HTTP URL within 60 seconds of the created_at
timestamp, it does not guarantee to the relay server that the HTTP client supplying this event is the author of that event. That is to say, replay attacks within 60 seconds are possible if the event leaks.
Relays Servers should consider whether any data they return to an authenticated HTTP connection is confidential. If so, it is strongly recommended that they only accept authentication and reply with said confidential information if the connection is secured with TLS (e.g. via https).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no relays involved in this flow. It only signs an event to take advantage of NIP-07, but the result is sent over an HTTP header.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A 60 second expiring token scoped to a specific URL is actually significantly better security than OAuth tokens, even with refresh tokens.
This is perhaps the most secure HTTP Auth method the world has ever seen.
This comment was marked as spam.
This comment was marked as spam.
This is interesting. I needed to create server-side events on behalf of users, and I solved a similar problem differently:
I have a very specific use-case (hacking Nostr support into a legacy system), but I thought you might think it's interesting. |
"created_at": 1682327852, | ||
"tags": [ | ||
[ | ||
"u", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why "u" instead of "url"? I think single letter tags are meant to be indexed by relays with filters such as { "#u": [...] }
Since the intention is not to index, I don't think it should be a single letter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I second this. I believe r
would also be valid here.
98.md
Outdated
Servers MUST perform the following checks in order to validate the event: | ||
1. The `kind` MUST be `27235`. | ||
2. The `created_at` MUST be within a reasonable time window (suggestion 60 seconds). | ||
3. The `url` tag MUST be exactly the same as the absolute request URL (including query parameters). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here you've called it "url" even though your example says "u", so I'm guessing you were trying to decide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea i prefer url
@melvincarvalho prefers u
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@melvincarvalho is wrong. 😃 That's my only remaining feedback, otherwise LGTM.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIP-94 uses the "url" tag already. Having "u" and "url" coexist isn't ideal. https://github.com/nostr-protocol/nips/blob/master/94.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am using it on two endpoints, but I would have no problem changing it. I'll adapt to what you all decide 🙂
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I support this NIP. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hate to bring this back up, but NIP 12 defines r
for url references. url
is used in NIP 94. Adding u
violates rule #4 for drafting NIPs. I think your implementation should lose vs the NIPs repo @melvincarvalho.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@staab but the same tag can have different meanings in two different contexts.
Another possible flow uses NIP-07's encrypt functionality. The server has a pubkey, which at some point the client obtains and stores. The I've been thinking about this problem a lot. |
I've drafted a protocol flow NIP for I think the best way forward is to define optional additional security layers that can be used. One could be tracking event_id and limiting to single use. Another would be using certificate pinning inside your application, to prevent ssl stripping / MIM. Ultimately we need a non-interactive auth protocol, and optionally an interactive one if people so desire. You can always add additional security like in #469 (comment) For an ephemeral event, the tag shouldn't matter as it's not going to be indexed anyway ( |
This comment was marked as spam.
This comment was marked as spam.
I'd still strongly opt to include the below elements in this NIP - the 402
Payment Required side can be omitted in full and an extension NIP without
issue.
The additional (protocol flow) questions that should be answered in this
NIP are:
1. What does a typical protocol auth flow look like? What are the cases I
need to support? How/When do I retry requests?
2. How do I know an endpoint supports Nostr HTTP Auth? -- proposed answer,
401 Unauthorised response with the WWW-Authenticate: NOSTR-NIP-98 header (I
actually don't care what the header value used here is - this just make
sense)
3. What happens when auth was successful, however access is not permitted -
403 Forbidden result. Sounds simple, however best clarified as part of the
spec to avoid incompatibilities and weird other status codes being picked.
It's important for client retry logic. Even if it's a SHOULD.
Why does this matter?
When writing a client lib/app that supports this NIP, the client needs to
be able to identify if failed access (401 Unauthorised) to an endpoint can
be granted using the Authorisation header, and that the client implements
an auth method available - using this NIP and re-sending the same request.
Secondly, the client needs to know in a standard way, the auth identity you
provided was decoded correctly (or it can 400/500), however it does not
grant access (i.e. 403 Forbidden) - however you can try again with another
auth identity. Or the client can clearly show UI that tells the user they
don't have access.
I've been using this NIP in production and development/prototypes, and the
above was needed to make use of this NIP.
On 20 May 2023 at 6:59:16 pm, Melvin Carvalho ***@***.***) wrote:
401 and 402 are a good idea, but I think it should be a separate NIP that
builds on this one. Not every NIP-98 implementer will want to implement
402, and there are also numerous ways to do 402 that exist. Adding on 402
may be slow down merging of a NIP that has growing implementations.
How about extension of nip-98 as new nips 98x -- 980, 981, 982 etc.
—
Reply to this email directly, view it on GitHub
<#469 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AADILOSISTONJGFXHTQWTM3XHDS5JANCNFSM6AAAAAAXJKU2PI>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
This comment was marked as spam.
This comment was marked as spam.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK, but I'd prefer to not see u
introduced. We only have 26 letters.
"created_at": 1682327852, | ||
"tags": [ | ||
[ | ||
"u", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I second this. I believe r
would also be valid here.
98.md
Outdated
Servers MUST perform the following checks in order to validate the event: | ||
1. The `kind` MUST be `27235`. | ||
2. The `created_at` MUST be within a reasonable time window (suggestion 60 seconds). | ||
3. The `url` tag MUST be exactly the same as the absolute request URL (including query parameters). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hate to bring this back up, but NIP 12 defines r
for url references. url
is used in NIP 94. Adding u
violates rule #4 for drafting NIPs. I think your implementation should lose vs the NIPs repo @melvincarvalho.
|
||
When the request contains a body (as in POST/PUT/PATCH methods) clients SHOULD include a SHA256 hash of the request body in a `payload` tag as hex (`["payload", "<sha256-hex>"]`), servers MAY check this to validate that the requested payload is authorized. | ||
|
||
If one of the checks was to fail the server SHOULD respond with a 401 Unauthorized response code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this should be 403 if the Authorization header was actually sent.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know what the constraints related to your project are, but if this is the correct usage of 401 that's fine, I'm not 100% clear on the nuances.
Doesn't https://ucan.xyz/ basically do serverless JWT for authorization like we're looking at for this? |
I implemented NIP-98 auth in Ditto: https://gitlab.com/soapbox-pub/ditto/-/blob/develop/src/middleware/auth98.ts So you can call Mastodon API endpoints like One idea which occurred to me... is that NIP-98 + proof of work is an epic combination for deterring spam and DDOS attacks. Nothing stops me from adding a NIP-13 You could use this during registration especially. However, POW with NIP-07 is a bit awkward. |
I implemented NIP-98 auth in a web component https://github.com/Dolu89/nostr-one Then you can use NIP-98 from nostr-tools that I implemented too, in your backend to validate the event. https://github.com/nbd-wtf/nostr-tools/blob/3368e8c00e728ff9b4981d58f8f7e546caa1d0f1/nip98.ts#L63C23-L63C36 |
Adding NIP-98 for HTTP Auth
This is currently used by the Snort backend api for managing NIP-5 addresses and the subscriptions system.
Nosdav is also using this spec @melvincarvalho
https://github.com/v0l/nips/blob/nip98/98.md