tag:github.com,2008:https://github.com/MasterKale/SimpleWebAuthn/releases
Release notes from SimpleWebAuthn
2024-07-23T04:40:07Z
tag:github.com,2008:Repository/266156053/v10.0.1
2024-07-23T04:47:33Z
v10.0.1
<h3>Packages</h3>
<ul>
<li>@simplewebauthn/server@10.0.1</li>
</ul>
<h3>Changes</h3>
<ul>
<li><strong>[server]</strong> <code>isoCrypto.verify()</code> now has better support for signature verification with ECC public keys using P-256, P-385, and P-521 curves (<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/594" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/594/hovercard">#594</a>, with thanks to <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/nlordell/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/nlordell">@nlordell</a>)</li>
</ul>
MasterKale
tag:github.com,2008:Repository/266156053/v10.0.0
2024-04-12T23:11:01Z
v10.0.0 - The one that goes up to 20
<p>Thanks for everything, Node 16 and Node 18, but it's time to move on! The headlining change of this<br>
release is the targeting of Node LTS v20+ as the minimum Node runtime. Additional developer-centric<br>
quality-of-life changes have also been made in the name of streamlining use of SimpleWebAuthn on<br>
both the back end and front end.</p>
<p>This release is packed with updates, so buckle up! Refactor advice for breaking changes is, as<br>
always, offered below.</p>
<h3>Packages</h3>
<ul>
<li>@simplewebauthn/browser@10.0.0</li>
<li>@simplewebauthn/server@10.0.0</li>
<li>@simplewebauthn/types@10.0.0</li>
</ul>
<h3>Changes</h3>
<ul>
<li><strong>[server]</strong> The minimum supported Node version has been raised to Node v20<br>
(<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/531" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/531/hovercard">#531</a>)</li>
<li><strong>[server]</strong> <code>user.displayName</code> now defaults to an empty string if a value is not specified for<br>
<code>userDisplayName</code> when calling <code>generateRegistrationOptions()</code><br>
(<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/538" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/538/hovercard">#538</a>)</li>
<li><strong>[browser]</strong> The <code>browserSupportsWebAuthnAutofill()</code> helper will no longer break in environments<br>
in which <code>PublicKeyCredential</code> is not present<br>
(<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/557" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/557/hovercard">#557</a>, with thanks to <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/clarafitzgerald/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/clarafitzgerald">@clarafitzgerald</a>)</li>
</ul>
<h3>Breaking Changes</h3>
<ul>
<li><strong>[server]</strong> The following breaking changes were made in PR<br>
<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/529" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/529/hovercard">#529</a>:
<ul>
<li><code>generateRegistrationOptions()</code> now expects <code>Base64URLString</code> for excluded credential IDs</li>
<li><code>generateAuthenticationOptions()</code> now expects <code>Base64URLString</code> for allowed credential IDs</li>
<li><code>credentialID</code> returned from response verification methods is now a <code>Base64URLString</code></li>
<li><code>AuthenticatorDevice.credentialID</code> is now a <code>Base64URLString</code></li>
<li><code>isoBase64URL.isBase64url()</code> is now called <code>isoBase64URL.isBase64URL()</code></li>
</ul>
</li>
<li><strong>[browser, server]</strong> The following breaking changes were made in PR<br>
<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/552" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/552/hovercard">#552</a>:
<ul>
<li><code>generateRegistrationOptions()</code> now accepts an optional <code>Uint8Array</code> instead of a <code>string</code> for<br>
<code>userID</code></li>
<li><code>isoBase64URL.toString()</code> and <code>isoBase64URL.fromString()</code> have been renamed</li>
<li><code>generateRegistrationOptions()</code> will now generate random user IDs</li>
<li><code>user.id</code> is now treated like a base64url string in <code>startRegistration()</code></li>
<li><code>userHandle</code> is now treated like a base64url string in <code>startAuthentication()</code></li>
</ul>
</li>
<li><strong>[server]</strong> <code>rpID</code> is now a required argument when calling <code>generateAuthenticationOptions()</code><br>
(<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/555" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/555/hovercard">#555</a>)</li>
</ul>
<hr>
<h4>[server] <code>generateRegistrationOptions()</code> now expects <code>Base64URLString</code> for excluded credential IDs</h4>
<p>The <code>isoBase64URL</code> helper can be used to massage <code>Uint8Array</code> credential IDs into base64url strings:</p>
<p><strong>Before</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="const opts = await generateRegistrationOptions({
// ...
excludeCredentials: devices.map((dev) => ({
id: dev.credentialID, // type: Uint8Array
type: 'public-key',
transports: dev.transports,
})),
});"><pre><span class="pl-k">const</span> <span class="pl-s1">opts</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">generateRegistrationOptions</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c">// ...</span>
<span class="pl-c1">excludeCredentials</span>: <span class="pl-s1">devices</span><span class="pl-kos">.</span><span class="pl-en">map</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-s1">dev</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">id</span>: <span class="pl-s1">dev</span><span class="pl-kos">.</span><span class="pl-c1">credentialID</span><span class="pl-kos">,</span> <span class="pl-c">// type: Uint8Array</span>
<span class="pl-c1">type</span>: <span class="pl-s">'public-key'</span><span class="pl-kos">,</span>
<span class="pl-c1">transports</span>: <span class="pl-s1">dev</span><span class="pl-kos">.</span><span class="pl-c1">transports</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p><strong>After</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="import { isoBase64URL } from '@simplewebauthn/server/helpers';
const opts = await generateRegistrationOptions({
// ...
excludeCredentials: devices.map((dev) => ({
id: isoBase64URL.fromBuffer(dev.credentialID), // type: string
transports: dev.transports,
})),
});"><pre><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">isoBase64URL</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@simplewebauthn/server/helpers'</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">opts</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">generateRegistrationOptions</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c">// ...</span>
<span class="pl-c1">excludeCredentials</span>: <span class="pl-s1">devices</span><span class="pl-kos">.</span><span class="pl-en">map</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-s1">dev</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">id</span>: <span class="pl-s1">isoBase64URL</span><span class="pl-kos">.</span><span class="pl-en">fromBuffer</span><span class="pl-kos">(</span><span class="pl-s1">dev</span><span class="pl-kos">.</span><span class="pl-c1">credentialID</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-c">// type: string</span>
<span class="pl-c1">transports</span>: <span class="pl-s1">dev</span><span class="pl-kos">.</span><span class="pl-c1">transports</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p>The <code>type</code> argument is no longer needed either.</p>
<hr>
<h4>[server] <code>generateAuthenticationOptions()</code> now expects <code>Base64URLString</code> for allowed credential IDs</h4>
<p>Similarly, the <code>isoBase64URL</code> helper can also be used during auth to massage <code>Uint8Array</code> credential<br>
IDs into base64url strings:</p>
<p><strong>Before</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="const opts = await generateAuthenticationOptions({
// ...
allowCredentials: devices.map((dev) => ({
id: dev.credentialID, // type: Uint8Array
type: 'public-key',
transports: dev.transports,
})),
});"><pre><span class="pl-k">const</span> <span class="pl-s1">opts</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">generateAuthenticationOptions</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c">// ...</span>
<span class="pl-c1">allowCredentials</span>: <span class="pl-s1">devices</span><span class="pl-kos">.</span><span class="pl-en">map</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-s1">dev</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">id</span>: <span class="pl-s1">dev</span><span class="pl-kos">.</span><span class="pl-c1">credentialID</span><span class="pl-kos">,</span> <span class="pl-c">// type: Uint8Array</span>
<span class="pl-c1">type</span>: <span class="pl-s">'public-key'</span><span class="pl-kos">,</span>
<span class="pl-c1">transports</span>: <span class="pl-s1">dev</span><span class="pl-kos">.</span><span class="pl-c1">transports</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p><strong>After</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="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,
})),
});"><pre><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">isoBase64URL</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@simplewebauthn/server/helpers'</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">opts</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">generateAuthenticationOptions</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c">// ...</span>
<span class="pl-c1">allowCredentials</span>: <span class="pl-s1">devices</span><span class="pl-kos">.</span><span class="pl-en">map</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-s1">dev</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">id</span>: <span class="pl-s1">isoBase64URL</span><span class="pl-kos">.</span><span class="pl-en">fromBuffer</span><span class="pl-kos">(</span><span class="pl-s1">dev</span><span class="pl-kos">.</span><span class="pl-c1">credentialID</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-c">// type: Base64URLString (a.k.a string)</span>
<span class="pl-c1">transports</span>: <span class="pl-s1">dev</span><span class="pl-kos">.</span><span class="pl-c1">transports</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p>The <code>type</code> argument is no longer needed either.</p>
<hr>
<h4>[server] <code>credentialID</code> returned from response verification methods is now a <code>Base64URLString</code></h4>
<p>It is no longer necessary to manually stringify <code>credentialID</code> out of response verification methods:</p>
<p><strong>Before</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="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,
});
}"><pre><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">isoBase64URL</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@simplewebauthn/server/helpers'</span><span class="pl-kos">;</span>
<span class="pl-c">// Registration</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> verified<span class="pl-kos">,</span> registrationInfo <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">verifyRegistrationResponse</span><span class="pl-kos">(</span><span class="pl-kos">{</span> ... <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">verified</span> <span class="pl-c1">&&</span> <span class="pl-s1">registrationInfo</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> credentialID <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-s1">registrationInfo</span><span class="pl-kos">;</span>
<span class="pl-k">await</span> <span class="pl-en">storeInDatabase</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">credIDString</span>: <span class="pl-s1">isoBase64URL</span><span class="pl-kos">.</span><span class="pl-en">fromBuffer</span><span class="pl-kos">(</span><span class="pl-s1">credentialID</span><span class="pl-kos">)</span><span class="pl-kos">,</span> ... <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-c">// Authentication</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> verified<span class="pl-kos">,</span> authenticationInfo <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">verifyAuthenticationResponse</span><span class="pl-kos">(</span><span class="pl-kos">{</span> ... <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">verified</span> <span class="pl-c1">&&</span> <span class="pl-s1">authenticationInfo</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> newCounter<span class="pl-kos">,</span> credentialID <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-s1">authenticationInfo</span><span class="pl-kos">;</span>
<span class="pl-s1">dbAuthenticator</span><span class="pl-kos">.</span><span class="pl-c1">counter</span> <span class="pl-c1">=</span> <span class="pl-s1">authenticationInfo</span><span class="pl-kos">.</span><span class="pl-c1">newCounter</span><span class="pl-kos">;</span>
<span class="pl-k">await</span> <span class="pl-en">updateCounterInDatabase</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">credIDString</span>: <span class="pl-s1">isoBase64URL</span><span class="pl-kos">.</span><span class="pl-en">fromBuffer</span><span class="pl-kos">(</span><span class="pl-s1">credentialID</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
newCounter<span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p><strong>After</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="// 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 });
}"><pre><span class="pl-c">// Registration</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> verified<span class="pl-kos">,</span> registrationInfo <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">verifyRegistrationResponse</span><span class="pl-kos">(</span><span class="pl-kos">{</span> ... <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">verified</span> <span class="pl-c1">&&</span> <span class="pl-s1">registrationInfo</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> credentialID <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-s1">registrationInfo</span><span class="pl-kos">;</span>
<span class="pl-k">await</span> <span class="pl-en">storeInDatabase</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">credIDString</span>: <span class="pl-s1">credentialID</span><span class="pl-kos">,</span> ... <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-c">// Authentication</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> verified<span class="pl-kos">,</span> authenticationInfo <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">verifyAuthenticationResponse</span><span class="pl-kos">(</span><span class="pl-kos">{</span> ... <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">verified</span> <span class="pl-c1">&&</span> <span class="pl-s1">authenticationInfo</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> newCounter<span class="pl-kos">,</span> credentialID <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-s1">authenticationInfo</span><span class="pl-kos">;</span>
<span class="pl-s1">dbAuthenticator</span><span class="pl-kos">.</span><span class="pl-c1">counter</span> <span class="pl-c1">=</span> <span class="pl-s1">authenticationInfo</span><span class="pl-kos">.</span><span class="pl-c1">newCounter</span><span class="pl-kos">;</span>
<span class="pl-k">await</span> <span class="pl-en">updateCounterInDatabase</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">credIDString</span>: <span class="pl-s1">credentialID</span><span class="pl-kos">,</span> newCounter <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<hr>
<h4>[server] <code>AuthenticatorDevice.credentialID</code> is now a <code>Base64URLString</code></h4>
<p>Calls to <code>verifyAuthenticationResponse()</code> will need to be updated to encode the credential ID to a<br>
base64url string:</p>
<p><strong>Before</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="const verification = await verifyAuthenticationResponse({
// ...
authenticator: {
// ...
credentialID: credIDBytes,
},
});"><pre><span class="pl-k">const</span> <span class="pl-s1">verification</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">verifyAuthenticationResponse</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c">// ...</span>
<span class="pl-c1">authenticator</span>: <span class="pl-kos">{</span>
<span class="pl-c">// ...</span>
<span class="pl-c1">credentialID</span>: <span class="pl-s1">credIDBytes</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p><strong>After</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="import { isoBase64URL } from '@simplewebauthn/server/helpers';
const verification = await verifyAuthenticationResponse({
// ...
authenticator: {
// ...
credentialID: isoBase64URL.fromBuffer(credIDBytes),
},
});"><pre><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">isoBase64URL</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@simplewebauthn/server/helpers'</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">verification</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">verifyAuthenticationResponse</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c">// ...</span>
<span class="pl-c1">authenticator</span>: <span class="pl-kos">{</span>
<span class="pl-c">// ...</span>
<span class="pl-c1">credentialID</span>: <span class="pl-s1">isoBase64URL</span><span class="pl-kos">.</span><span class="pl-en">fromBuffer</span><span class="pl-kos">(</span><span class="pl-s1">credIDBytes</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<hr>
<h4>[server] <code>isoBase64URL.isBase64url()</code> is now called <code>isoBase64URL.isBase64URL()</code></h4>
<p>Note the capitalization change from "url" to "URL" in the method name. Update calls to this method<br>
accordingly.</p>
<hr>
<h4>[server] <code>generateRegistrationOptions()</code> will now generate random user IDs</h4>
<h4>[browser] <code>user.id</code> is now treated like a base64url string in <code>startRegistration()</code></h4>
<h4>[browser] <code>userHandle</code> is now treated like a base64url string in <code>startAuthentication()</code></h4>
<p>A random identifier will now be generated when a value is not provided for the now-optional <code>userID</code><br>
argument when calling <code>generateRegistrationOptions()</code>. This identifier will be <strong>base64url-encoded<br>
string of 32 random bytes</strong>. RPs that wish to take advantage of this can <strong>simply omit this<br>
argument</strong>.</p>
<p>Additionally, <code>startRegistration()</code> will base64url-decode <code>user.id</code> before calling WebAuthn. During<br>
auth <code>startAuthentication()</code> will base64url-encode <code>userHandle</code> in the returned credential. This<br>
should be a transparent change for RP's that simply feed <strong>@simplewebauthn/server</strong> options output<br>
into the corresponding <strong>@simplewebauthn/browser</strong> methods.</p>
<p>However, RP's that wish to continue generating their own user identifiers will need to take<br>
additional steps to ensure they get back user IDs in the expected format after authentication.</p>
<p><strong>Before (SimpleWebAuthn v9)</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="// @simplewebauthn/server v9.x
const opts = generateRegistrationOptions({
// ...
userID: 'randomUserID',
});"><pre><span class="pl-c">// @simplewebauthn/server v9.x</span>
<span class="pl-k">const</span> <span class="pl-s1">opts</span> <span class="pl-c1">=</span> <span class="pl-en">generateRegistrationOptions</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c">// ...</span>
<span class="pl-c1">userID</span>: <span class="pl-s">'randomUserID'</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="// @simplewebauthn/browser v9.x
const credential = await startAuthentication(...);
sendToServer(credential);"><pre><span class="pl-c">// @simplewebauthn/browser v9.x</span>
<span class="pl-k">const</span> <span class="pl-s1">credential</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">startAuthentication</span><span class="pl-kos">(</span>...<span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-en">sendToServer</span><span class="pl-kos">(</span><span class="pl-s1">credential</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="// @simplewebauthn/server v9.x
const credential = await receiveFromBrowser();
console.log(
credential.response.userhandle, // 'randomUserID'
);"><pre><span class="pl-c">// @simplewebauthn/server v9.x</span>
<span class="pl-k">const</span> <span class="pl-s1">credential</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">receiveFromBrowser</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span>
<span class="pl-s1">credential</span><span class="pl-kos">.</span><span class="pl-c1">response</span><span class="pl-kos">.</span><span class="pl-c1">userhandle</span><span class="pl-kos">,</span> <span class="pl-c">// 'randomUserID'</span>
<span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p><strong>After (SimpleWebAuthn v10)</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="// @simplewebauthn/server v10.x
import { isoUint8Array } from '@simplewebauthn/server/helpers';
const opts = generateRegistrationOptions({
// ...
userID: isoUint8Array.fromUTF8String('randomUserID'),
});"><pre><span class="pl-c">// @simplewebauthn/server v10.x</span>
<span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">isoUint8Array</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@simplewebauthn/server/helpers'</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">opts</span> <span class="pl-c1">=</span> <span class="pl-en">generateRegistrationOptions</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c">// ...</span>
<span class="pl-c1">userID</span>: <span class="pl-s1">isoUint8Array</span><span class="pl-kos">.</span><span class="pl-en">fromUTF8String</span><span class="pl-kos">(</span><span class="pl-s">'randomUserID'</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="// @simplewebauthn/browser v10.x
const credential = await startAuthentication(...);
sendToServer(credential);"><pre><span class="pl-c">// @simplewebauthn/browser v10.x</span>
<span class="pl-k">const</span> <span class="pl-s1">credential</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">startAuthentication</span><span class="pl-kos">(</span>...<span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-en">sendToServer</span><span class="pl-kos">(</span><span class="pl-s1">credential</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="// @simplewebauthn/server v10.x
import { isoBase64URL } from '@simplewebauthn/server/helpers';
const credential = await receiveFromBrowser();
console.log(
isoBase64URL.toUTF8String(credential.response.userhandle), // 'randomUserID'
);"><pre><span class="pl-c">// @simplewebauthn/server v10.x</span>
<span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">isoBase64URL</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@simplewebauthn/server/helpers'</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">credential</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">receiveFromBrowser</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span>
<span class="pl-s1">isoBase64URL</span><span class="pl-kos">.</span><span class="pl-en">toUTF8String</span><span class="pl-kos">(</span><span class="pl-s1">credential</span><span class="pl-kos">.</span><span class="pl-c1">response</span><span class="pl-kos">.</span><span class="pl-c1">userhandle</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-c">// 'randomUserID'</span>
<span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<hr>
<h4>[server] <code>isoBase64URL.toString()</code> and <code>isoBase64URL.fromString()</code> have been renamed</h4>
<p>The method names have been updated to reflect the use of UTF-8 string encoding:</p>
<p><strong>Before:</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="const foo = isoBase64URL.toString('...');
const bar = isoBase64URL.fromString('...');"><pre><span class="pl-k">const</span> <span class="pl-s1">foo</span> <span class="pl-c1">=</span> <span class="pl-s1">isoBase64URL</span><span class="pl-kos">.</span><span class="pl-en">toString</span><span class="pl-kos">(</span><span class="pl-s">'...'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">bar</span> <span class="pl-c1">=</span> <span class="pl-s1">isoBase64URL</span><span class="pl-kos">.</span><span class="pl-en">fromString</span><span class="pl-kos">(</span><span class="pl-s">'...'</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p><strong>After:</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="const foo = isoBase64URL.toUTF8String('...');
const bar = isoBase64URL.fromUTF8String('...');"><pre><span class="pl-k">const</span> <span class="pl-s1">foo</span> <span class="pl-c1">=</span> <span class="pl-s1">isoBase64URL</span><span class="pl-kos">.</span><span class="pl-en">toUTF8String</span><span class="pl-kos">(</span><span class="pl-s">'...'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">bar</span> <span class="pl-c1">=</span> <span class="pl-s1">isoBase64URL</span><span class="pl-kos">.</span><span class="pl-en">fromUTF8String</span><span class="pl-kos">(</span><span class="pl-s">'...'</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<hr>
<h4>[server] <code>rpID</code> is now a required argument when calling <code>generateAuthenticationOptions()</code></h4>
<p>Update calls to this method to specify the same <code>rpID</code> as passed into<br>
<code>generateRegistrationOptions()</code>:</p>
<p><strong>Before</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content=" generateRegistrationOptions({ rpID: 'example.com', ... });
generateAuthenticationOptions();"><pre> <span class="pl-en">generateRegistrationOptions</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">rpID</span>: <span class="pl-s">'example.com'</span><span class="pl-kos">,</span> ... <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-en">generateAuthenticationOptions</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p><strong>After</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content=" generateRegistrationOptions({ rpID: 'example.com', ... });
generateAuthenticationOptions({ rpID: 'example.com' });"><pre> <span class="pl-en">generateRegistrationOptions</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">rpID</span>: <span class="pl-s">'example.com'</span><span class="pl-kos">,</span> ... <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-en">generateAuthenticationOptions</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">rpID</span>: <span class="pl-s">'example.com'</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
MasterKale
tag:github.com,2008:Repository/266156053/v9.0.3
2024-02-16T16:45:48Z
v9.0.3
<h3>Packages</h3>
<ul>
<li>@simplewebauthn/server@9.0.3</li>
</ul>
<h3>Changes</h3>
<ul>
<li><strong>[server]</strong> Fixed <code>"Cannot find module 'cbor-x/index-no-eval' or its corresponding type declarations"</code> build errors when transpiling TypeScript projects using <strong>@simplewebauthn/server</strong> (<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/521" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/521/hovercard">#521</a>)</li>
</ul>
MasterKale
tag:github.com,2008:Repository/266156053/v9.0.2
2024-02-28T19:46:31Z
v9.0.2
<h3>Packages</h3>
<ul>
<li>@simplewebauthn/server@9.0.2</li>
</ul>
<h3>Changes</h3>
<ul>
<li><strong>[server]</strong> Improved support for Next.js Edge runtime (<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/518" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/518/hovercard">#518</a>, with thanks to <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/balazsorban44/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/balazsorban44">@balazsorban44</a>)</li>
</ul>
MasterKale
tag:github.com,2008:Repository/266156053/v9.0.1
2024-01-26T06:15:59Z
v9.0.1
<h3>Packages</h3>
<ul>
<li>@simplewebauthn/browser@9.0.1</li>
<li>@simplewebauthn/server@9.0.1</li>
<li>@simplewebauthn/types@9.0.1</li>
</ul>
<h3>Changes</h3>
<ul>
<li><strong>[server]</strong> Fixed an issue with use with CBOR handling in runtime environments that restrict use of <code>eval()</code> (<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/511" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/511/hovercard">#511</a>, with thanks to <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/Maronato/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/Maronato">@Maronato</a>)</li>
<li><strong>[browser, types]</strong> Monorepo version sync</li>
</ul>
MasterKale
tag:github.com,2008:Repository/266156053/v9.0.0
2024-01-21T06:10:20Z
v9.0.0 - The one in which 11 characters were saved
<h3>Packages</h3>
<ul>
<li>@simplewebauthn/browser@9.0.0</li>
<li>@simplewebauthn/server@9.0.0</li>
<li>@simplewebauthn/types@9.0.0</li>
</ul>
<h3>Changes</h3>
<ul>
<li><strong>[types]</strong> The <code>@simplewebauthn/typescript-types</code> package has been renamed to<br>
<code>@simplewebauthn/types</code> (<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/508" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/508/hovercard">#508</a>)</li>
</ul>
<h3>Breaking Changes</h3>
<ul>
<li>Any reference to <code>@simplwebauthn/typescript-types</code> will need to be replaced with the new package name <code>@simplewebauthn/types</code>:</li>
</ul>
<p><strong>Before:</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="import { ... } from '@simplwebauthn/typescript-types';"><pre><span class="pl-k">import</span> <span class="pl-kos">{</span> ... <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@simplwebauthn/typescript-types'</span><span class="pl-kos">;</span></pre></div>
<p><strong>After:</strong></p>
<div class="highlight highlight-source-shell notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$> npm uninstall @simplewebauthn/typescript-types
$> npm install -D @simplewebauthn/types"><pre>$<span class="pl-k">></span> npm uninstall @simplewebauthn/typescript-types
$<span class="pl-k">></span> npm install -D @simplewebauthn/types</pre></div>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="import { ... } from '@simplwebauthn/types';"><pre><span class="pl-k">import</span> <span class="pl-kos">{</span> ... <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@simplwebauthn/types'</span><span class="pl-kos">;</span></pre></div>
MasterKale
tag:github.com,2008:Repository/266156053/v8.3.7
2024-01-20T06:56:47Z
v8.3.7
<h3>Packages</h3>
<ul>
<li>@simplewebauthn/browser@8.3.7</li>
<li>@simplewebauthn/server@8.3.7</li>
</ul>
<h3>Changes</h3>
<ul>
<li><strong>[browser]</strong> The <code>WebAuthnError</code> class can now be imported from <code>@simplewebauthn/browser</code> for simpler error detection and handling when calling <code>startRegistration()</code> and <code>startAuthentication()</code> (<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/505" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/505/hovercard">#505</a>, with thanks to <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/zoontek/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/zoontek">@zoontek</a>)</li>
<li><strong>[server]</strong> The <code>COSEPublicKeyEC2</code>, <code>COSEPublicKeyOKP</code>, and <code>COSEPublicKeyRSA</code> types can now be imported from <code>@simplwebauthn/server/helpers</code> to help type possible return values from <code>decodeCredentialPublicKey()</code> (<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/504" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/504/hovercard">#504</a>, with thanks to <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/mmv08/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/mmv08">@mmv08</a>)</li>
<li><strong>[server]</strong> Custom challenge strings passed to <code>generateRegistrationOptions()</code> will now be treated as UTF-8 strings to align with the existing behavior of <code>generateAuthenticationOptions()</code> (<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/507" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/507/hovercard">#507</a>)</li>
</ul>
MasterKale
tag:github.com,2008:Repository/266156053/v8.3.6
2023-12-29T22:59:40Z
v8.3.6
<h3>Packages</h3>
<ul>
<li>@simplewebauthn/server@8.3.6</li>
</ul>
<h3>Changes</h3>
<ul>
<li><strong>[server]</strong> Updated dependencies to fix an issue with ASN.1 parsing when calling<br>
<code>verifyAuthenticationResponse()</code> (<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/499" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/499/hovercard">#499</a>)</li>
</ul>
MasterKale
tag:github.com,2008:Repository/266156053/v8.3.5
2023-10-28T04:10:12Z
v8.3.5
<h3>Packages</h3>
<ul>
<li>@simplewebauthn/server@8.3.5</li>
</ul>
<h3>Changes</h3>
<ul>
<li><strong>[server]</strong> Use of the Web Crypto API in edge runtimes has been improved<br>
(<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/472" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/472/hovercard">#472</a>)</li>
</ul>
MasterKale
tag:github.com,2008:Repository/266156053/v8.3.4
2023-10-27T20:35:22Z
v8.3.4
<h3>Packages</h3>
<ul>
<li>@simplewebauthn/browser@.3.4</li>
<li>@simplewebauthn/server@.3.4</li>
<li>@simplewebauthn/typescript-types@.3.4</li>
</ul>
<h3>Changes</h3>
<ul>
<li><strong>[server]</strong> The library will now try to use <code>globalThis.crypto</code> first before trying to import<br>
Node's <code>node:crypto</code> as a fallback (<a href="https://github.com/MasterKale/SimpleWebAuthn/pull/468" data-hovercard-type="pull_request" data-hovercard-url="/MasterKale/SimpleWebAuthn/pull/468/hovercard">#468</a>)</li>
<li><strong>[browser, types]</strong> Version sync</li>
</ul>
MasterKale