ceramicnetwork / js-did

A simple interface to interact with DIDs that conform to the DID-provider interface.
Other
96 stars 28 forks source link

Add support for passkeys/webauthn #166

Closed telamon closed 11 months ago

telamon commented 1 year ago

Add support for passkeys/webauthn

Status: wip

Description

This PR adds support for using hardware tags and OS authenticators .

How Has This Been Tested?

Definition of Done

Before submitting this PR, please make sure:

References:

R&D Notes / Worklog

telamon commented 1 year ago

Glad you brought this up.
I attempted to save adopters from the headache of when to probe by doing it "on-demand" but after closer inspection it seems I just caused the headache elsewhere.

The methods createDID() and probeDIDs() are straightforward.
The difference is that the DID becomes settled/known after AuthMethod was used instead of after the probe. The end result is identical to original implementation but at a lower cost of complexity and a clearer API. (I think yes :+1: )

I considered the following scenarios: authenticator state browser state user-choice Action button presses Outcome
* unknown New ID createDID(); getAuthMethod(did) 2 success
has key known New ID forget();createDID(); getAuthMethod(did) 2 success
has key known Login getAuthMethod(did) 1 success
has key unknown Login probeDIDs(); getAuthMethod(candidates) 2 success
empty unknown Login probeDIDs(); 1 fail

API Proposal

Given the new methods; I would update the example in the README.md to:

// New identity
const did = await createDID('label')
await storeSomehow(did)
const authMethod = await getAuthMethod({ did })

// Returning user
const did = await getSomehow()
let authMethod

if (did) { // previous DID found
  authMethod = await getAuthMethod({ did })
} else { // new device detected
  const dids = await probeDIDs()
  authMethod = await getAuthMethod({ dids })
}

const session = await DIDSession.authorize(authMethod, { resources: [...] })
await storeSomehow(session.did.id) // Save for later
oed commented 1 year ago

Thanks for the suggested api. A few questions/notes:

  1. in createDID('label'), what is the purpose of the parameter?
  2. Maybe make a comment about what the developer should do rather than include methods names not exported by this library.
  3. You need to store session.did.parent, not session.did.id (the latter is just the session key)
telamon commented 1 year ago

@oed 2,3 gotcha!

1: The label is the name of the credential, it's what's displayed in OS-popups on discoverable ops: image

different question

if probe returns two fully qualified did-strings, does that mean we export one false DID? Not sure what to call it but gut tells me to ask if we should be proactive about preventing accidental storage/transmission of not-yet-settled DIDs.

const recoveryToken = await probe()

const authMethod = getAuthMethod({ did, recoveryToken })

Is this a concern?

oed commented 1 year ago

1 . Makes sense. What happens if there are two keys with the same label?

4 . Ok, makes sense to call it something like recoveryToken as you suggest.

5 . I wonder if we should also add another option in getAuthMethod that would allow you to pass a function as follows:


function selectDID(did1: string , did2: string): Promise<did>

const authMethod = getAuthMethod({ selectDID })

Calling getAuthMethod would result in one signature to sign the CACAO, then the user could resolve the correct DID in whichever way they want.

telamon commented 1 year ago
  1. They are more or less shown as dupes (isolated to domain): image

(webauthn does not export functions to remove/relabel credentials, so we can't really do much more here.)

  1. The pure functional probe approach you suggested gives a solid API and provides developers freedom in implementation.
    Using a select callback is valid, but let it be known that I went down that path by accident.. (I failed to recognize that the order of probe() and sign() does not matter for recovery). The cons of conditional callbacks is that they are more difficult to debug/trigger, and affects only those who wish to use passkeys in a decentralized manner.

Thinking "There should be one clear way to do it.": I cast my vote on the manual probe instead of callback; (makes more sense, causes less developer strain shrug)

oed commented 1 year ago

I would prefer if we could do both probe and callback approaches.

For example in Ceramic we could store a document after the first time we authenticate. This would allow a developer to query documents for the two DIDs and if it finds one of them it knows which is the correct DID.