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) =&gt; ({ 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">=&gt;</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) =&gt; ({ 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">=&gt;</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) =&gt; ({ 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">=&gt;</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) =&gt; ({ 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">=&gt;</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 &amp;&amp; registrationInfo) { const { credentialID } = registrationInfo; await storeInDatabase({ credIDString: isoBase64URL.fromBuffer(credentialID), ... }); } // Authentication const { verified, authenticationInfo } = await verifyAuthenticationResponse({ ... }); if (verified &amp;&amp; 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">&amp;&amp;</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">&amp;&amp;</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 &amp;&amp; registrationInfo) { const { credentialID } = registrationInfo; await storeInDatabase({ credIDString: credentialID, ... }); } // Authentication const { verified, authenticationInfo } = await verifyAuthenticationResponse({ ... }); if (verified &amp;&amp; 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">&amp;&amp;</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">&amp;&amp;</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="$&gt; npm uninstall @simplewebauthn/typescript-types $&gt; npm install -D @simplewebauthn/types"><pre>$<span class="pl-k">&gt;</span> npm uninstall @simplewebauthn/typescript-types $<span class="pl-k">&gt;</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