Skip to content

bicycle-codes/webauthn-keys

Repository files navigation

webauthn keys

tests types module semantic versioning install size license

A simple way to use crypto keys with webauthn (biometric authentication).

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

See a live demo

Contents

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.

get started

first session

Create a new keypair.

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

const id = await create({  // create a new user
    username: 'alice'
})

Save the new user to indexedDB

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

await pushLocalIdentity(id.localID, id.record)

Login with this user

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

// ... sometime in the future, login again ...

const localID = buttonElement.dataset.localId
const authResult = await auth(localID!)

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

Link to the file you copied.

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

example

Create a new keypair

Create a new keypair, and keep it secret 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. Default is domain name
})

Save public data to indexedDB

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)

get a persisted keypair

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


develop

Tip

You can use the browser dev tools to setup a virtual authenticator

start a local server

npm start

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()

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

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.

About

Use the Web Authentication API to store crypto keys

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •