npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@bicycle-codes/webauthn-keys

v0.0.11

Published

Use ECC keys with the webauthn API

Downloads

36

Readme

webauthn keys

tests types module semantic versioning install size license

A simple way to use crypto keys, protected by webauthn (biometric authentication).

Save an ECC keypair, then access it iff the user authenticates via webauthn.

See a live demo

install

npm i -S @bicycle-codes/webauthn-keys

how it works

We save the iv of the our keypair, which lets us re-create the same keypair on subsequent sessions.

The secret iv is set in the user.id property in a PublicKeyCredentialCreationOptions object. The browser saves the credential, and will only read it after successful authentication with the webauthn API.

[!NOTE]
We are not using the webcrypto API for creating keys, because we are waiting on ECC support in all browsers.

[!NOTE]
We only need 1 keypair for both signing and encrypting. Internally, we create 2 keypairs -- one for signing and one for encryption -- but this is hidden from the interface.

Use

This exposes ESM via package.json exports field.

ESM

import {
    create,
    getKeys,
    encrypt,
    decrypt,
    signData,
    verify,
    toBase64String,
    fromBase64String,
    localIdentities,
    storeLocalIdentities,
    pushLocalIdentity,
} from '@bicycle-codes/webauthn-keys'

// and types
import type {
    Identity,
    RegistrationResult,
    LockKey,
    JSONValue,
    AuthResponse
} from '@bicycle-codes/webauthn-keys'

pre-built JS

This package exposes minified JS files too. Copy them to a location that is accessible to your web server, then link to them in HTML.

copy

cp ./node_modules/@bicycle-codes/package/dist/index.min.js ./public/webauthn-keys.min.js

HTML

<script type="module" src="./webauthn-keys.min.js"></script>

example

Create a new keypair, and protect it with the webatuhn API.

import { create } from '@bicycle-codes/webauthn-keys'

const id = await create({
    username: 'alice',  // unique within relying party (this device)
    displayName: 'Alice Example',  // human-readable name
    relyingPartyName: 'Example application'  // rp.name
})

Save the public data of the new ID to indexedDB.

import { pushLocalIdentity } from '@bicycle-codes/webauthn-keys'

// save to indexedDB
await pushLocalIdentity(id.localID, id.record)

Login again, and get the same keypair in memory. This will prompt for biometric authentication.

import { auth, getKeys } from '@bicycle-codes/webauthn-keys'

const authResult = await auth()
const keys = getKeys(authResult)

See also

API

create

Create a new keypair. The relying party ID defaults to the current location.hostname.

async function create (
    lockKey = deriveLockKey(),
    opts:Partial<{
        username:string
        displayName:string
        relyingPartyID:string
        relyingPartyName:string
    }> = {
        username: 'local-user',
        displayName: 'Local User',
        relyingPartyID: document.location.hostname,
        relyingPartyName: 'wacg'
    }
):Promise<{ localID:string, record:Identity, keys:LockKey }>

create example

import {
    create,
    pushLocalIdentity
} from '@bicycle-codes/webauthn-keys'

const { record, keys, localID } = await create(undefined, {
    username: 'alice',
    displayName: 'Alice Example',
    relyingPartyID: location.hostname,
    relyingPartyName: 'Example application'
})

//
// Save the ID to indexedDB.
// This saves public info only, not keys.
//
await pushLocalIdentity(id.localID, record)

auth

Prompt the user for authentication with webauthn.

async function auth (
    opts:Partial<CredentialRequestOptions> = {}
):Promise<PublicKeyCredential & { response:AuthenticatorAssertionResponse }>

auth example

import { auth, getKeys } from '@bicycle-codes/webauthn'

const authResult = await auth()
const keys = getKeys(authResult)

pushLocalIdentity

Take the localId created by the create call, and save it to indexedDB.

async function pushLocalIdentity (localId:string, id:Identity):Promise<void>

pushLocalIdentity example

const id = await create({
    username,
    relyingPartyName: 'Example application'
})
await pushLocalIdentity(id.localID, id.record)

getKeys

Authenticate with a saved identity; takes the response from auth().

function getKeys (opts:(PublicKeyCredential & {
    response:AuthenticatorAssertionResponse
})):LockKey

getKeys example

import { getKeys, auth } from '@bicycle-codes/webauthn-keys'

// authenticate
const authData = await auth()

// get keys from auth response
const keys = getKeys(authData)

stringify

Return a base64 encoded string of the given public key.

function stringify (keys:LockKey):string

stringify example

import { stringify } from '@bicycle-codes/webauthn-keys'

const keyString = stringify(myKeys)
// => 'welOX9O96R6WH0S8cqqwMlPAJ3VwMgAZEnc1wa1MN70='

signData

export async function signData (data:string|Uint8Array, key:LockKey, opts?:{
    outputFormat?:'base64'|'raw'
}):Promise<Uint8Array>

signData example

import { signData, deriveLockKey } from '@bicycle-codes/webauthn-keys'

// create a new keypair
const key = await deriveLockKey()

const sig = await signData('hello world', key)
// => INZ2A9Lt/zL6Uf6d6D6fNi95xSGYDiUpK3tr/zz5a9iYyG5u...

verify

Check that the given signature is valid with the given data.

export async function verify (
    data:string|Uint8Array,
    sig:string|Uint8Array,
    keys:{ publicKey:Uint8Array|string }
):Promise<boolean>

verify example

import { verify } from '@bicycle-codes/webauthn-keys'

const isOk = await verify('hello', 'dxKmG3oTEN2i23N9d...', {
    publicKey: '...'  // Uint8Array or string
})
// => true

encrypt

export function encrypt (
    data:JSONValue,
    lockKey:LockKey,
    opts:{
        outputFormat:'base64'|'raw';
    } = { outputFormat: 'base64' }
// return type depends on the given output format
):string|Uint8Array

encrypt example

import { encrypt } from '@bicycle-codes/webauthn-keys'

const encrypted = encrypt('hello encryption', myKeys)
// => XcxWEwijaHq2u7aui6BBYGjIrjVTkLIS5...

decrypt

function decrypt (
    data:string|Uint8Array,
    lockKey:LockKey,
    opts:{ outputFormat?:'utf8'|'raw', parseJSON?:boolean } = {
        outputFormat: 'utf8',
        parseJSON: true
    }
):string|Uint8Array|JSONValue

decrypt example

import { decrypt } from '@bicycle-codes/webauthn-keys'

const decrypted = decrypt('XcxWEwijaHq2u7aui6B...', myKeys, {
    parseJSON: false
})

// => 'hello encryption'

localIdentities

Load local identities from indexed DB, return a dictionary from user ID to the identity record.

async function localIdentities ():Promise<Record<string, Identity>>

localIdentities example

import { localIdentites } from '@bicycle-codes/webauthn-keys'

const ids = await localIdentities()

develop

start a local server

npm start

test

Run some automated tests of the cryptography API, not webauthn.

start tests & watch for file changes

npm test

run tests and exit

npm run test:ci

see also

What's the WebAuthn User Handle (response.userHandle)?

Its primary function is to enable the authenticator to map a set of credentials (passkeys) to a specific user account.

A secondary use of the User Handle (response.userHandle) is to allow authenticators to know when to replace an existing resident key (discoverable credential) with a new one during the registration ceremony.

libsodium docs

credits

This is heavily influenced by @lo-fi/local-data-lock and @lo-fi/webauthn-local-client. Thanks @lo-fi organization and @getify for working in open source; this would not have been possible otherwise.