Login with Nostr
I think Nostr makes a good candidate to be used as a very simple DID layer. Having "Login with Nostr" auth on websites solves a lot of problems in a very elegant way, and Nostr's main use case as a social network protocol makes it highly suited to be used as your main identity proving key.
Compare "Login with Nostr" to similar "Login with Lightning" (LNURL-auth) specs to see some easy and obvious advantages:
Remote signer vs local signer
Login with Lightning requires access to remote keys, login with Nostr requires access to local keys ideally stored in a browser extension. Due to the way Lightning works you can only really have one instance. You need all your client devices linked to a single Lightning node, this means most clients will be connecting to the signer remotely. Now if your Lightning node goes down or you lose your connection you also can't auth with any service. This could cause circular dependencies where you lose the connection to your Lightning node so you can't auth with the services you need to access to debug the issue with your Lightning node like your hosting provider or VPN account. You could technically solve this by replicating your LN keys to other client devices only to be used for local auth signing but that introduces other risks.
Unique identifier vs identity
A Lightning node is not really an identity but a unique identifier. It just tells you the person that auths is the same random person that authed last time, it doesn't tell you who they are. A nostr pubkey is an identity. It tells you who they are, what their name is, what they look like, who they know, how you can pay them, how you can message them.
This is much more useful as an identity layer for an application. The application can show their profile picture, username, send secure cross platform push notifications via NIP-04 encrypted Nostr DMs, etc.
Consistent identity across services
Lightning pubkeys are sensitive private information and can leak confidential financial information, Nostr pubkeys are safe to share with anyone. LNURL-auth adds extra steps to solve this by creating derived subkeys for identities that are unique to each service you auth with. This does not seem ideal, it seems the default case is that an identity is something that you do want to follow you across all your accounts. Nostr based auth behaves more appropriate in this regard. In the rare case you need to achieve privacy and separation between certain services you can still do that by using use a throwaway Nostr key for those services.
User relationships across services
Since authing with Nostr shares a real social identity with the service, they can also see your Nostr social graph. This could be useful for connecting you to people you already know on the new service.
Low cost identity
Ideally identities should be easy to create but hard to build up reputation to limit spam while avoiding excluding people from the network. It's not clear that it will be cost effective / scalable for everyone to run their own Lightning node so tying individual identity to a single Lightning node pubkey is problematic. Nostr keys are easy to create and hard reputation can be earned via PoW/DNS or building a strong social graph.
Implementation ideas
This could be done today without any spec changes by just using existing NIP-07 functionality with something like:
const pubkey = await window.nostr.getPublicKey()
const authEvent = createAuthEvent({service: 'auth.example.com', challenge})
const signature = await window.nostr.signEvent(authEvent)
if (!isValidSignature(pubkey, signature)) throw new AuthenticationError()
const relays = await window.nostr.getRelays()
const {display_name, picture, nip05} = await fetchNostrMetadata(pubkey, relays)
However I think it would make a lot of sense to standardise this as part of the window.nostr spec. We already need some standardisation to agree on the auth event format so it can be safely communicated to the user what they are approving by signing anyway. It would also be nice to abstract this into a single function that all services would depend on instead of everyone having to roll their own. Additionally it would be helpful for the extension to directly pass over the Nostr user metadata since it probably already has it locally, instead of requiring every single website to directly interface with Nostr and worry about tracking it down from relays.
With a new proposed window.nostr.authenticate() method the logic would be
const {pubkey, signature, display_name, picture, nip05} = await window.nostr.authenticate(challenge)
The service string would be set to be the calling origin. You should do some server side validation of challenge/signature before using this ID for access control. You may also now do additional querying over Nostr directly with pubkey for any additional application specific things you want to do, note that this is not required if you just need the basic profile metadata. This makes it very simple for generic websites (not nostr web clients) to integrate with Nostr for auth. It's just one line of zero dependency code on the frontend and one challenge signature verification on the backend.
I'm interested in anyones thoughts on this approach and if anyone thinks it would be worth spending more time formalising this into a NIP.
// cc some people who may be interested in this @fiatjaf @jb55 @Cameri @bumi
Edit: Improved protocol using URI handlers for native/mobile app support: https://github.com/nostr-protocol/nips/issues/154#issuecomment-1374559486
Concept ACK. Should be trivial to implement in Nos2x. But this only provides identity with an app directly on the browser. I would want this to work with the proposed NIP called Remote Event Signing.
@Cameri thanks was not familiar with Remote Event Signing, definitely makes sense to support that too.
Perhaps it could be useful to split this into two different NIPs. A new NIP to define the auth note schema (which both browser extensions and remote signers would know how to interpret). And separately an extension to NIP-07 to add the window.nostr.authenticate() method.
agree that login with nostr offers a lot of wins!
we implemented "Login With Nostr" on https://vida.page via a simple 2-Factor DM flow. it's a UX users are familiar with and works on web+mobile. (one of the big issues with relying on window or browser extensions is that the flow won't work on mobile)
basically the user gives us a pubkey or a nip-05. if nip-05 we check for relays and auto fill, if just pubkey we also ask them for a relay. then we send them a pin via encrypted DM and ask them to input the pin. if successful, we will pull their public details via the same relay and provision their account.
I have a little library I made with helper functions based on @fiatjaf's nostr-tools lib to make the flow easy. just need to clean it up and will publish if anyone is interested but none of the flow is complex.
one way to clean this up from a UX perspective in clients would be to agree on a format for the DM that is sent so that clients could automatically pop-up an alert with the pin (similar to Apple's 2-factor auth) if it receives a specifically formatted message.
So, service sends the DM, user opens their client and is immediately presented the pin.
however i suppose this could be a nightmare if spammers started sending you tons of these specifically formatted messages.
@lylepratt that's a very cool approach, good point re browser APIs not working on mobile.
One issue you could have with a DM based approach is going through a relay. I really like how my above approach does not rely on Nostr for networking, only for signing, it makes auth much more reliable.
Since Nostr only kinda half guarantees that stuff ends up where it should, it seems not ideal to rely on going through relays for something critical like auth. For example what if I have my client configured so low PoW DMs are ignored. I wouldn't see your DM if you don't add enough PoW. Even if you add PoW what if my client is configured to ~$1 PoW per DM, can you justify paying that to log a user in? Or what if my main relay goes down and you can't find me, I can't authenticate with any services until it comes back online.
I wonder if we could use the same approach I detailed above but using URI handlers to jump between apps instead of relying on browser extensions. Browser extensions can still work this way by taking over the URI handlers, but so can native apps too.
The web app (or even a native app) could encode a payload like:
{
service: 'auth.vida.page',
challenge: 'ba65d3b01e6bc841050a3525133d38d2',
callback: 'https://auth.vida.page?nauth_response=<token>'
}
into a Nostr URI like:
nostr:nauth1blahblahblahblah
Which gets handled by your native Nostr client app and decoded so no actual networking is required.
The client app then knows to prompt the user if they want to log in to auth.vida.page, signs the challenge, encodes the signature along with user metadata into a token and opens https://auth.vida.page?nauth_response=blah which can then be caught and handled by the original website (or app via URI handler).
The original service can now decode the token, verify the challenge/response sig server side and has all the users metadata. Will work on mobile and desktop, native apps and web apps.
Thoughts?
@lylepratt that's a very cool approach, good point re browser APIs not working on mobile.
One issue you could have with a DM based approach is going through a relay. I really like how my above approach does not rely on Nostr for networking, only for signing, it makes auth much more reliable.
Since Nostr only kinda half guarantees that stuff ends up where it should, it seems not ideal to rely on going through relays for something critical like auth. For example what if I have my client configured so low PoW DMs are ignored. I wouldn't see your DM if you don't add enough PoW. Even if you add PoW what if my client is configured to ~$1 PoW per DM, can you justify paying that to log a user in? Or what if my main relay goes down and you can't find me, I can't authenticate with any services until it comes back online.
I wonder if we could use the same approach you explained above but using URI handlers to jump between apps instead of Nostr DMs.
The web app (or even a native app) could encode a payload like:
{ service: 'auth.vida.page', challenge: 'ba65d3b01e6bc841050a3525133d38d2', callback: 'https://auth.vida.page?nauth_response=<token>' }into a Nostr URI like:
nostr:nauth1blahblahblahblahWhich gets handled by your native Nostr client app and decoded so no actual networking is required.
The client app then knows to prompt the user if they want to log in to
auth.vida.page, signs the challenge, encodes the signature along with user metadata into a token and openshttps://auth.vida.page?nauth_response=blahwhich can then be caught and handled by the original website (or app via URI handler).The original service can now decode the token, verify the challenge/response sig server side and has all the users metadata.
Thoughts?
I prefer this approach much more.
I prefer this approach much more.
You mean prefer it to DMs or prefer it to window.nostr.authenticate()?
I prefer this approach much more.
You mean prefer it to DMs or prefer it to
window.nostr.authenticate()?
I prefer URI handlers as opposed to DMs. I think we still need to support window.nostr.authenticate() if available, otherwise fall back to URI handler. Sorry for the confusion.
Yep I agree, window.nostr.authenticate() is complimentary to the URI handlers since it's detectable and can be automated without user interaction.
I like the encoded payload idea. it's very similar to this idea I tried to explain here: https://twitter.com/lylepratt/status/1610829070928908288?s=46&t=HyWgoORCb6b2g5WZD7RRug
"This is an idea for a NIP that would make it easy for external apps, services, and APIs to automatically integrate with Nostr clients.
-
Sign a message containing a timestamp within X seconds (or minutes) of “now”.
-
Client appends the signed message as a URL param (or via POST) in supported links that get presented to users.
-
The Service sees the param, fetches and provisions profile info if needed, and grants access to the service to the user.
I’m going to try to do something like this on Vida. For example, it would let us make it easy for Nostr clients to link a user directly into an audio/video spaces room (or even pull external streams into the app directly via API). Lots of possibilities for other stuff too!"
Relevant perhaps: https://github.com/nostr-protocol/nips/blob/329cd8d8a19fd1dcb12df7eb7a5d640b2a5b6bf8/19.md#shareable-identifiers-with-extra-metadata
Why not just request the user to sign an event? I fail to understand why this added complexity is accomplishing.
@Cameri niceee, that looks perfect for this!
Why not just request the user to sign an event? I fail to understand why this added complexity is accomplishing.
Could that not be dangerous?
If apps just send out generic events for auth then what stops a malicious site MITMing a user and requesting them to sign a challenge for a different site? It may not be clear to the user what they are signing from the payload. If some kind of auth object is standardised then it can show a very clear message explain that this is logging the user in to example.com and block attempts to sign this message from origins other than example.com.
Of course if we make a super safe multi-step protocol that can be pretty safe, but I am thinking about the fallback case, i.e. do you need a new implementation from every client for this to work? Or can clients that do not implement this but that can still sign arbitrary events still work, even with worse UX?
Also to TLDR:
Why not just request the user to sign an event?
That basically is all that's happening here but in a standardised way so:
- Any Nostr signing app/extension/website can sign for any other app/website without having to manually copy/paste JSON strings around
- It can be done in a secure way that prevents MITM attacks
- It explains to users what they are doing without expecting them to understand the meaning behind the JSON string
do you need a new implementation from every client for this to work? Or can clients that not implement this but that can still sign arbitrary events still work, even with worse UX?
Yes if you can sign a note you can authenticate with this protocol. But if your signer didn't understand this protocol you'd need to manually copy/paste JSON between the apps instead of clicking buttons, and you wouldn't get a warning if evil.com asks you to sign a challenge for coinbase.com.
To make this more generic instead of nostr:nauth1blahblahblah maybe it could be nostr:nsign1blahblahblah which when clicked asks whichever app has registered a URI handler for nostr to sign the encoded note.
Then Nostr clients can optionally detect an app authentication note and parse the metadata and follow this protocol. Dumb clients would just blindly sign which could be dangerous if the user doesn't understand what the JSON means and you'd have to paste the result back over in the initial app.
display_name, picture, nip05 nope, signer extensions should not have to connect to relays. this could be added if the signing extension has a config option for what metadata it should offer to websites but otherwise apps should query kind 0 themselves.
Hmm, yes, thinking better about this I think it makes sense to have the nostr:... something and the "fallback" case I had in mind isn't really possible at all today so we're not losing anything by going with a more manual approach. The browser extensions can just intercept clicks on these nauth links and handle them natively. I like this very much.
display_name, picture, nip05nope, signer extensions should not have to connect to relays. this could be added if the signing extension has a config option for what metadata it should offer to websites but otherwise apps should query kind 0 themselves.
Yes, but I would say this shouldn't even be optional, it should be forbidden. The apps should just fetch metadata directly if they want. What the signing app should do is give a relay URL to the website from where they can fetch the metadata.
Shouldn't this thing be somehow merged with the "Nostr Connect" proposal and also with the Relay Authentication" proposal? These are all separate things, but they may share something in common.
Yes, but I would say this shouldn't even be optional, it should be forbidden. The apps should just fetch metadata directly if they want. What the signing app should do is give a relay URL to the website from where they can fetch the metadata.
preferred relays are available
To make this more generic instead of
nostr:nauth1blahblahblahmaybe it could benostr:nsign1blahblahblahwhich when clicked asks whichever app has registered a URI handler fornostrto sign the encoded note.
please use a binary encoding for nsign, thanks, since encoding text in any kind of baseX creates wasted space. it doesn't even have to be complicated:
- 32 byte pubkey
- 4 byte created_at
- 4 byte tags length
- json tags
- 4 byte content length
- content
issue: how will callbacks be handled?
Shouldn't this thing be somehow merged with the "Nostr Connect" proposal and also with the Relay Authentication" proposal? These are all separate things, but they may share something in common.
relay auth, yes so we can define a standardized auth event for any context (relay, website, etc). nostr connect, no since it allows for remote requesting of signing not anything else.
@Semisol @fiatjaf good points re not returning metadata. I was thinking it made it super simple for sites to integrate without having to do too much Nsotr stuff, but agree it pushes too much complexity over to the signer.
Shouldn't this thing be somehow merged with the "Nostr Connect" proposal and also with the Relay Authentication" proposal? These are all separate things, but they may share something in common.
I'm not familiar with those but will look into them and see what can be shared.
By the way, lnurl-auth has gained a lot of love in the past years and many users and websites seem to love the idea, however the protocol has a bunch of annoying flaws and UX issues in my opinion. Me and @Dolu89 and some others I don't remember were discussing replacing it with a better protocol, and in my mind that could be a protocol generic enough to support keys from other sources other than Bitcoin wallets -- i.e. if you can produce a bip340 signature you can have an identity and login to a website.
Shouldn't we make this generic enough to allow this to be that unified protocol? Could be just by using a generic url scheme like keyauth:... and then producing a normal signature over a challenge, not a Nostr event specifically.
By the way,
lnurl-authhas gained a lot of love in the past years and many users and websites seem to love the idea, however the protocol has a bunch of annoying flaws and UX issues in my opinion. Me and @Dolu89 and some others I don't remember were discussing replacing it with a better protocol, and in my mind that could be a protocol generic enough to support keys from other sources other than Bitcoin wallets -- i.e. if you can produce a bip340 signature you can have an identity and login to a website.Shouldn't we make this generic enough to allow this to be that unified protocol? Could be just by using a generic url scheme like
keyauth:...and then producing a normal signature over a challenge, not a Nostr event specifically.
I would love this! But should we support Nostr first before we go nuts trying to support everything like with DID methods?
simple keyauth proposal:
- clients have a public/private keypair
- the URI scheme is
keyauth://example.com/endpoint?challenge=challenge - client should sign sha256 of the JSON
["keyauth", <keyauth URI>, <unix epoch seconds at time of signing>]encoded without unnecessary whitespace, and should send a POST tohttps://example.com/endpoint?challenge=challenge(along with any other query parameters in thekeyauth://URI) with the following added query parameterspk: pubkey encoded as hexsigned_at: the unix epoch that was used for signingsig: the signature as hex
The signature must contain the domain name otherwise it can be phished. I would do this change:
-client should sign sha256 of the JSON ["keyauth", <challenge string>, <unix epoch seconds at time of signing>] encoded without unnecessary whitespace, and should send a POST to https://example.com/endpoint?challenge=challenge (along with any other query parameters in the keyauth:// URI) with the following added query parameters
+client should sign sha256 of the JSON ["keyauth", "keyauth://example.com/endpoint?challenge=challenge", <unix epoch seconds at time of signing>] encoded without unnecessary whitespace, and should send a POST to https://example.com/endpoint?challenge=challenge (along with any other query parameters in the keyauth:// URI) with the following added query parameters
But should we support Nostr first before we go nuts trying to support everything like with DID methods?
I think we should support just bip340 signatures. That includes Bitcoin wallets, Nostr clients and other things that may show up later and decide to reuse our network-effect. They will be forced to use bip340 signatures and nothing more than that.