WebAuthn, Simplified. A collection of TypeScript-first libraries for simpler WebAuthn integration. Supports modern browsers, Node, Deno, and more.
MIT License
Bot releases are hidden (Show)
Published by MasterKale 6 months ago
Thanks for everything, Node 16 and Node 18, but it's time to move on! The headlining change of this
release is the targeting of Node LTS v20+ as the minimum Node runtime. Additional developer-centric
quality-of-life changes have also been made in the name of streamlining use of SimpleWebAuthn on
both the back end and front end.
This release is packed with updates, so buckle up! Refactor advice for breaking changes is, as
always, offered below.
user.displayName
now defaults to an empty string if a value is not specified foruserDisplayName
when calling generateRegistrationOptions()
browserSupportsWebAuthnAutofill()
helper will no longer break in environmentsPublicKeyCredential
is not presentgenerateRegistrationOptions()
now expects Base64URLString
for excluded credential IDsgenerateAuthenticationOptions()
now expects Base64URLString
for allowed credential IDscredentialID
returned from response verification methods is now a Base64URLString
AuthenticatorDevice.credentialID
is now a Base64URLString
isoBase64URL.isBase64url()
is now called isoBase64URL.isBase64URL()
generateRegistrationOptions()
now accepts an optional Uint8Array
instead of a string
foruserID
isoBase64URL.toString()
and isoBase64URL.fromString()
have been renamedgenerateRegistrationOptions()
will now generate random user IDsuser.id
is now treated like a base64url string in startRegistration()
userHandle
is now treated like a base64url string in startAuthentication()
rpID
is now a required argument when calling generateAuthenticationOptions()
generateRegistrationOptions()
now expects Base64URLString
for excluded credential IDsThe isoBase64URL
helper can be used to massage Uint8Array
credential IDs into base64url strings:
Before
const opts = await generateRegistrationOptions({
// ...
excludeCredentials: devices.map((dev) => ({
id: dev.credentialID, // type: Uint8Array
type: 'public-key',
transports: dev.transports,
})),
});
After
import { isoBase64URL } from '@simplewebauthn/server/helpers';
const opts = await generateRegistrationOptions({
// ...
excludeCredentials: devices.map((dev) => ({
id: isoBase64URL.fromBuffer(dev.credentialID), // type: string
transports: dev.transports,
})),
});
The type
argument is no longer needed either.
generateAuthenticationOptions()
now expects Base64URLString
for allowed credential IDsSimilarly, the isoBase64URL
helper can also be used during auth to massage Uint8Array
credential
IDs into base64url strings:
Before
const opts = await generateAuthenticationOptions({
// ...
allowCredentials: devices.map((dev) => ({
id: dev.credentialID, // type: Uint8Array
type: 'public-key',
transports: dev.transports,
})),
});
After
import { isoBase64URL } from '@simplewebauthn/server/helpers';
const opts = await generateAuthenticationOptions({
// ...
allowCredentials: devices.map((dev) => ({
id: isoBase64URL.fromBuffer(dev.credentialID), // type: Base64URLString (a.k.a string)
transports: dev.transports,
})),
});
The type
argument is no longer needed either.
credentialID
returned from response verification methods is now a Base64URLString
It is no longer necessary to manually stringify credentialID
out of response verification methods:
Before
import { isoBase64URL } from '@simplewebauthn/server/helpers';
// Registration
const { verified, registrationInfo } = await verifyRegistrationResponse({ ... });
if (verified && registrationInfo) {
const { credentialID } = registrationInfo;
await storeInDatabase({ credIDString: isoBase64URL.fromBuffer(credentialID), ... });
}
// Authentication
const { verified, authenticationInfo } = await verifyAuthenticationResponse({ ... });
if (verified && authenticationInfo) {
const { newCounter, credentialID } = authenticationInfo;
dbAuthenticator.counter = authenticationInfo.newCounter;
await updateCounterInDatabase({
credIDString: isoBase64URL.fromBuffer(credentialID),
newCounter,
});
}
After
// Registration
const { verified, registrationInfo } = await verifyRegistrationResponse({ ... });
if (verified && registrationInfo) {
const { credentialID } = registrationInfo;
await storeInDatabase({ credIDString: credentialID, ... });
}
// Authentication
const { verified, authenticationInfo } = await verifyAuthenticationResponse({ ... });
if (verified && authenticationInfo) {
const { newCounter, credentialID } = authenticationInfo;
dbAuthenticator.counter = authenticationInfo.newCounter;
await updateCounterInDatabase({ credIDString: credentialID, newCounter });
}
AuthenticatorDevice.credentialID
is now a Base64URLString
Calls to verifyAuthenticationResponse()
will need to be updated to encode the credential ID to a
base64url string:
Before
const verification = await verifyAuthenticationResponse({
// ...
authenticator: {
// ...
credentialID: credIDBytes,
},
});
After
import { isoBase64URL } from '@simplewebauthn/server/helpers';
const verification = await verifyAuthenticationResponse({
// ...
authenticator: {
// ...
credentialID: isoBase64URL.fromBuffer(credIDBytes),
},
});
isoBase64URL.isBase64url()
is now called isoBase64URL.isBase64URL()
Note the capitalization change from "url" to "URL" in the method name. Update calls to this method
accordingly.
generateRegistrationOptions()
will now generate random user IDsuser.id
is now treated like a base64url string in startRegistration()
userHandle
is now treated like a base64url string in startAuthentication()
A random identifier will now be generated when a value is not provided for the now-optional userID
argument when calling generateRegistrationOptions()
. This identifier will be base64url-encoded
string of 32 random bytes. RPs that wish to take advantage of this can simply omit this
argument.
Additionally, startRegistration()
will base64url-decode user.id
before calling WebAuthn. During
auth startAuthentication()
will base64url-encode userHandle
in the returned credential. This
should be a transparent change for RP's that simply feed @simplewebauthn/server options output
into the corresponding @simplewebauthn/browser methods.
However, RP's that wish to continue generating their own user identifiers will need to take
additional steps to ensure they get back user IDs in the expected format after authentication.
Before (SimpleWebAuthn v9)
// @simplewebauthn/server v9.x
const opts = generateRegistrationOptions({
// ...
userID: 'randomUserID',
});
// @simplewebauthn/browser v9.x
const credential = await startAuthentication(...);
sendToServer(credential);
// @simplewebauthn/server v9.x
const credential = await receiveFromBrowser();
console.log(
credential.response.userhandle, // 'randomUserID'
);
After (SimpleWebAuthn v10)
// @simplewebauthn/server v10.x
import { isoUint8Array } from '@simplewebauthn/server/helpers';
const opts = generateRegistrationOptions({
// ...
userID: isoUint8Array.fromUTF8String('randomUserID'),
});
// @simplewebauthn/browser v10.x
const credential = await startAuthentication(...);
sendToServer(credential);
// @simplewebauthn/server v10.x
import { isoBase64URL } from '@simplewebauthn/server/helpers';
const credential = await receiveFromBrowser();
console.log(
isoBase64URL.toUTF8String(credential.response.userhandle), // 'randomUserID'
);
isoBase64URL.toString()
and isoBase64URL.fromString()
have been renamedThe method names have been updated to reflect the use of UTF-8 string encoding:
Before:
const foo = isoBase64URL.toString('...');
const bar = isoBase64URL.fromString('...');
After:
const foo = isoBase64URL.toUTF8String('...');
const bar = isoBase64URL.fromUTF8String('...');
rpID
is now a required argument when calling generateAuthenticationOptions()
Update calls to this method to specify the same rpID
as passed into
generateRegistrationOptions()
:
Before
generateRegistrationOptions({ rpID: 'example.com', ... });
generateAuthenticationOptions();
After
generateRegistrationOptions({ rpID: 'example.com', ... });
generateAuthenticationOptions({ rpID: 'example.com' });
"Cannot find module 'cbor-x/index-no-eval' or its corresponding type declarations"
build errors when transpiling TypeScript projects using @simplewebauthn/server (#521)eval()
(#511, with thanks to @Maronato)Published by MasterKale 9 months ago
@simplewebauthn/typescript-types
package has been renamed to@simplewebauthn/types
(#508)@simplwebauthn/typescript-types
will need to be replaced with the new package name @simplewebauthn/types
:Before:
import { ... } from '@simplwebauthn/typescript-types';
After:
$> npm uninstall @simplewebauthn/typescript-types
$> npm install -D @simplewebauthn/types
import { ... } from '@simplwebauthn/types';
WebAuthnError
class can now be imported from @simplewebauthn/browser
for simpler error detection and handling when calling startRegistration()
and startAuthentication()
(#505, with thanks to @zoontek)COSEPublicKeyEC2
, COSEPublicKeyOKP
, and COSEPublicKeyRSA
types can now be imported from @simplwebauthn/server/helpers
to help type possible return values from decodeCredentialPublicKey()
(#504, with thanks to @mmv08)generateRegistrationOptions()
will now be treated as UTF-8 strings to align with the existing behavior of generateAuthenticationOptions()
(#507)verifyAuthenticationResponse()
(#499)globalThis.crypto
first before trying to importnode:crypto
as a fallback (#468)deno vendor
will no longer error out because typescript-types/src/dom.tsstream
APIstartAuthentication(..., true)
to set up conditional UI will now require"webauthn"
value in an input's autocomplete="..."
be either the only value or the lastWebAuthnAbortService
singleton can now be imported, with a cancelCeremony()
startRegistration()
will visibly warn in the browser console when a WebAuthn API method call errors out in a way that is likely due to a browser extension intercepting the API. The original error is exposed as the cause
property on the error. (#447)startRegistration()
will no longer error out on registration responses generatedbase64URLStringToBuffer()
and bufferToBase64URLString()
are now@simplewebauthn/browser
(#444)verifyRegistrationResponse()
and verifyAuthenticationResponse()
now accept a newexpectedType
argument that can be used to, for example, verify Secure Payment ConfirmationMetadataService
has been disabledexpectedChallenge
argument for verifyRegistrationResponse()
andverifyAuthenticationResponse()
methods now also accept asynchronous methods"type": "module"
in their package.json will no longer errorPublished by MasterKale about 1 year ago
This major release marks the completion of a long journey that started with the release of v7.0.0:
SimpleWebAuthn is now available for use in non-Node projects! 🎉
SimpleWebAuthn debuted in mid-2020 as a combination of libraries aiming to make WebAuthn simpler to
use across browsers and "NodeJS + CommonJS" applications. Since then NodeJS has evolved to gain ESM
support, and additional JavaScript and TypeScript runtimes have debuted that offer ESM-centric,
TypeScript-first alternatives while also implementing Web APIs to offer a more consistent and
capable execution environment for developers.
I've wanted to make this project available to developers using these Node alternatives to help them
get past some of WebAuthn's rough spots. Today I'm happy to announce that this goal has been
achieved! 😌
See the Changes below for more information, as well as additional information on breaking
changes made in this release.
generateRegistrationOptions()
and generateAuthenticationOptions()
are nowPromise
that's now returnedgenerateChallenge()
(in @simplewebauthn/server/helpers
) is now an asynchronousPromise
that's now returned in whatever way