<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Passwordless.ID News & Articles]]></title><description><![CDATA[Passwordless.ID News & Articles]]></description><link>https://blog.passwordless.id</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1665491778333/WkebZmr2O.png</url><title>Passwordless.ID News &amp; Articles</title><link>https://blog.passwordless.id</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 21 Apr 2026 13:08:05 GMT</lastBuildDate><atom:link href="https://blog.passwordless.id/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Passkeys / WebAuthn Library v2.0 is there! 🎉]]></title><description><![CDATA[Hello folks,
I'm pleased to announce the release of the v2.0 of my WebAuthn library!

This library greatly simplifies the usage of passkeys by invoking the WebAuthn protocol more conveniently. It is open source, opinionated, dependency-free and minim...]]></description><link>https://blog.passwordless.id/passkeys-webauthn-library-v20-is-there</link><guid isPermaLink="true">https://blog.passwordless.id/passkeys-webauthn-library-v20-is-there</guid><category><![CDATA[#webauthn]]></category><category><![CDATA[passkeys]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Tue, 13 Aug 2024 14:53:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1723560731530/e07b0121-902e-4178-80d8-3d6616a71644.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello folks,</p>
<p>I'm pleased to announce the release of the v2.0 of my <a target="_blank" href="https://webauthn.passwordless.id">WebAuthn library</a>!</p>
<blockquote>
<p>This library greatly simplifies the usage of <strong>passkeys</strong> by invoking the <a target="_blank" href="https://w3c.github.io/webauthn/">WebAuthn protocol</a> more conveniently. It is <a target="_blank" href="https://github.com/passwordless-id/webauthn">open source</a>, opinionated, dependency-free and minimalistic (9kb only).</p>
</blockquote>
<h2 id="heading-demos">👀 Demos</h2>
<ul>
<li><a target="_blank" href="https://webauthn.passwordless.id/demos/basic.html">Basic Demo</a></li>
<li><a target="_blank" href="https://webauthn.passwordless.id/demos/conditional-ui.html">Autocomplete with conditional mediation</a></li>
<li><a target="_blank" href="https://webauthn.passwordless.id/demos/authenticators.html">Authenticators list</a></li>
<li><a target="_blank" href="https://webauthn.passwordless.id/demos/playground.html">Testing Playground</a></li>
</ul>
<p>These demos are plain HTML/JS, not minimized. Just open the sources in your browser if you are curious.</p>
<h2 id="heading-installation">📦 Installation</h2>
<h3 id="heading-modules-recommended">Modules (recommended)</h3>
<pre><code class="lang-bash">npm install @passwordless-id/webauthn
</code></pre>
<p>The base package contains both client and server side modules. You can import the <code>client</code> submodule or the <code>server</code> depending on your needs.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> {client} <span class="hljs-keyword">from</span> <span class="hljs-string">'@passwordless-id/webauthn'</span>
<span class="hljs-keyword">import</span> {server} <span class="hljs-keyword">from</span> <span class="hljs-string">'@passwordless-id/webauthn'</span>
</code></pre>
<p><em>Note: the brackets in the import are important!</em></p>
<h3 id="heading-alternatives">Alternatives</h3>
<p>For <strong>browsers</strong>, it can be imported using a CDN link in the page, or even inside the script itself.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> {client} <span class="hljs-keyword">from</span> src=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/@passwordless-id/webauthn@2.0.0/dist/webauthn.min.js"</span>
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Lastly, a <strong>CommonJS</strong> variant is also available for old Node stacks, to be imported using <code>require('@passwordless-id/webauthn')</code>. It's usage is discouraged though, in favor of the default ES modules.</p>
<p>Note that at least NodeJS <strong>19+</strong> is necessary. (The reason is that previous Node versions had no <code>WebCrypto</code> being globally available, making it impossible to have a "universal build")</p>
<h2 id="heading-getting-started">🚀 Getting started</h2>
<p>There are multiple ways to use and invoke the WebAuthn protocol.
What follows is just an example of the most straightforward use case. </p>
<h3 id="heading-registration">Registration</h3>
<pre><code><span class="hljs-keyword">import</span> {client} <span class="hljs-keyword">from</span> <span class="hljs-string">'@passwordless-id/webauthn'</span>
<span class="hljs-keyword">await</span> client.register({
  <span class="hljs-attr">challenge</span>: <span class="hljs-string">'a random string generated by the server'</span>,
  <span class="hljs-attr">user</span>: <span class="hljs-string">'John Doe'</span>
})
</code></pre><p>By default, this registers a passkey on any authenticator (local or roaming) with <code>preferred</code> user verification. For further options, see <a target="_blank" href="https://webauthn.passwordless.id/registration/">→ Registration docs</a></p>
<h3 id="heading-authentication">Authentication</h3>
<pre><code><span class="hljs-keyword">import</span> {client} <span class="hljs-keyword">from</span> <span class="hljs-string">'@passwordless-id/webauthn'</span>
<span class="hljs-keyword">await</span> client.authenticate({
  <span class="hljs-attr">challenge</span>: <span class="hljs-string">'a random string generated by the server'</span>
})
</code></pre><p>By default, this triggers the native passkey selection dialog, for any authenticator (local or roaming) and with  <code>preferred</code> user verification. For further options, see <a target="_blank" href="https://webauthn.passwordless.id/authentication/">→ Authentication docs</a></p>
<h3 id="heading-verification">Verification</h3>
<pre><code><span class="hljs-keyword">import</span> {server} <span class="hljs-keyword">from</span> <span class="hljs-string">'@passwordless-id/webauthn'</span>
<span class="hljs-keyword">await</span> server.verifyRegistration(registration, expected)
<span class="hljs-keyword">await</span> server.verifyAuthentication(registration, expected)
</code></pre><p>Look at the docs for <a target="_blank" href="https://webauthn.passwordless.id/registration/">registration</a> and <a target="_blank" href="https://webauthn.passwordless.id/authentication/">authentication</a> for the corresponding verification examples.
Or simply interact with real-life examples in the <a target="_blank" href="https://webauthn.passwordless.id/demos/playground.html">Testing Playground</a>.</p>
<h2 id="heading-why-a-version-2">⁉️ Why a "Version 2"?</h2>
<p>Nobody likes breaking changes, so what's the reason for it? The "Version 2" is not only a complete overhaul of the first version, it also differs from the previous mainly regarding its default behavior and "intermediate payloads". The said, it remains similar, striving for simplicity and ease-of-use.</p>
<h3 id="heading-a-bit-of-for-security-keys">A bit of ❤️ for security keys</h3>
<p>Previously, this lib defaulted to using the platform as authenticator if possible. The user experience was improved that way, going straight to user verification instead of intermediate popup(s) to select the authenticator. It was a smooth experience.</p>
<p>This behavior was born from a time where credentials were always device-bound. The terms "synced credentials" and "passkeys" did not even exist when the initial version of this lib was made. Nowadays, most platforms / authenticators now sync credentials in the cloud. While this is certainly convenient, the security and privacy guarantees are not as strong as with device-bound credentials. That is why security keys now deserve some love, since they are the only ones providing such strong guarantees. </p>
<p>The new behavior is to let the user choose the authenticator by default. It's also the native protocol's default. We want to give the choice to use security <em>by default</em>, since they <em>are</em> more secure.</p>
<p>In order to better support for security keys, some defaults also changed, like the user verification now being <code>preferred</code> and being discoverable also being <code>preferred</code>. Both should allow a broader set of security keys to be usable by default.</p>
<h3 id="heading-reflects-latest-protocol-changes">Reflects latest protocol changes</h3>
<p>The protocol evolved with time, and still is. For example, autocomplete with "conditional mediation" was added. It's an addition I'm not fan of, but certainly has its merits. It basically let's you trigger authentication using the autocomplete feature of a username input field. This is especially useful because there is no way to know if a credential has already been registered for the user or not. However, be wary that this feature is not universally supported for all platforms / browsers / authenticators.</p>
<p>Another recent change is the usage of <code>hints</code> ("security-key", "client-device", "hybrid"), which should replace the <code>authenticatorAttachment</code> and <code>transports</code> properties in the long term. They were kind of wacky anyway. That said, only Chrome supports hints for now, but it's handled in a backwards-compatible way by this library.</p>
<h3 id="heading-payloads-compatible-with-other-server-side-libs">Payloads compatible with other server-side libs</h3>
<p>This is another crucial large change. The response format has been changed completely to be compatible with the output as the <code>PublicKeyCredential.toJson()</code> method. An official part of the spec that only FireFox implements. </p>
<p>That way, it is possible to use the browser-side of this library and use almost any other server-side library for your favorite platform. Using this intermediate format increases compatibility cross-libraries in the long term.</p>
<h2 id="heading-last-words">🙏 Last words </h2>
<p>The original project https://passwordless.id is currently in limbo between proof-of-concept and production-ready. It would simply take more time than I can afford. However, I wanted to wrap up the changes in this library, since the scope is way smaller, and it seems more used by a wider range of people. It also follows the same goal: getting rid of annoying passwords while providing more security. Although I wanted to achieve this goal on a grander scale, this library should at least help others to use passkeys more easily.</p>
<p>...but it's kind of crazy that I do this. Actually, I'm currently on holiday and I should rather play with my kids outside rather than writing this article. Well, I'm still kind of an old nerd too. I just wanted to wrap it up. Consider this lib stable, use it to your heart's content and if you want to help the open-source ecosystem keep its balance, consider sponsoring this project too.</p>
<p>Have a nice week,
Arnaud</p>
]]></content:encoded></item><item><title><![CDATA[Passkeys F.A.Q.]]></title><description><![CDATA[The WebAuthn protocol is more than 200 pages long, it's complex and gets constantly tweaked. Moreover, the reality of browsers and authenticators have their own quirks and deviate from the official RFC. As such, all information on the web should be t...]]></description><link>https://blog.passwordless.id/passkeys-faq</link><guid isPermaLink="true">https://blog.passwordless.id/passkeys-faq</guid><category><![CDATA[passkeys]]></category><category><![CDATA[#webauthn]]></category><category><![CDATA[authentication]]></category><category><![CDATA[Passwordless]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Sun, 26 May 2024 19:19:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716750786062/acca0e0a-db05-44a9-b622-9e9c13b1d59c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>The WebAuthn protocol is more than 200 pages long, it's complex and gets constantly tweaked. Moreover, the reality of browsers and authenticators have their own quirks and deviate from the official RFC. As such, all information on the web should be taken with a grain of salt.</p>
<p>Also, there is some confusion regarding where passkeys are stored because the protocol evolved quite a bit in the past few years. In the beginning, "public key credentials" were hardware-bound. Then, major vendors pushed their agenda with "passkeys" synced with the user account in the cloud. Then, even password managers joined in with synced accounts shared with the whole family for example.</p>
<p>How the protocol works, and its security implications, became fuzzier and more nuanced.</p>
</blockquote>
<h2 id="heading-what-is-a-passkey">What <em>is</em> a passkey?</h2>
<p>Depending on who you ask, the answer may vary. According to the W3C specifications, it's a <strong>discoverable</strong> public key credential.</p>
<blockquote>
<p>If you ask me, that's a pretty dumb definition. Calling <strong>any</strong> public key credential a passkey would have been more straightforward.</p>
</blockquote>
<h2 id="heading-what-is-an-authenticator">What is an authenticator?</h2>
<p>The authenticator is the hardware or software that issues public key credentials and signs the authentication payload.</p>
<p>Hardware authenticators are typically security keys or the device itself using a dedicated chip. Software authenticators are password managers, either built-in in the platform or as dedicated app.</p>
<h2 id="heading-is-the-passkey-hardware-bound-or-synced-in-the-cloud">Is the passkey hardware-bound or synced in the cloud?</h2>
<p>It depends. It can be either and it's up to the <em>authenticator</em> to decide.</p>
<p>In the past, where security keys pioneered the field, hardware-bound keys were the norm. However, now that the big three (Apple, Google, Microsoft) built it in directly in their platform, software-bound keys, synced with the platform's user account in the cloud became the norm. These are sometimes also dubbed "multi-device" credentials.</p>
<h2 id="heading-can-i-decide-if-the-created-credential-should-be-hardware-bound-or-synced">Can I decide if the created credential should be hardware-bound or synced?</h2>
<p>Sadly, that is something only the authenticator can decide. You cannot influence whether the passkey should be synced or not, nor can you filter the authenticators that can be used.</p>
<blockquote>
<p>Concerns have been raised many times in the RFC, see <a target="_blank" href="https://github.com/w3c/webauthn/issues/1714">issue #1714</a>, <a target="_blank" href="https://github.com/w3c/webauthn/issues/1739">issue #1739</a> and <a target="_blank" href="https://github.com/w3c/webauthn/issues/1688">issue #1688</a> among others (and voice your opinion!).</p>
</blockquote>
<h2 id="heading-are-passkeys-a-form-of-2fa">Are passkeys a form of 2FA?</h2>
<p>Not by default. Passkeys are a single step 2FA only if:</p>
<ul>
<li><p>The credential is hardware-bound, not <code>synced</code>. Then this first factor is "something you possess".</p>
</li>
<li><p>The flag <code>userVerification</code> is <code>required</code>. Then this second factor is "something you are" (biometrics) or "something you know" (PIN code).</p>
</li>
</ul>
<blockquote>
<p>While requiring user verification would be ideal, this also restrict the hardware authenticators that can be used. Not all USB security keys have fingerprint sensor or PIN.</p>
</blockquote>
<h2 id="heading-are-hardware-bound-credentials-more-secure-than-synced-ones">Are hardware-bound credentials more secure than synced ones?</h2>
<p><em>Yes</em>. When the credential is hardware-bound, the security guarantees are straightforward. You must possess the device. Extremely simple and effective.</p>
<p>When using synced "multi-device" passkeys, the "cloud" has the key, your devices have the key, and the key is in-transit over the wire. While vendors go to great length to secure every aspect, it is still exposed to more risk. All security guarantees are hereby delegated to the software authenticator, whether it's built-in in the platform or a password manager. At best, these passkeys are as safe as the main account itself. If the account is hacked, whether it's by a stolen password, temporary access to your device or a lax recovery procedure, all the passkeys would come along with the hacked account. While it offers convenience, the security guarantees are not as strong as with hardware bound authenticators.</p>
<p>The privacy concerns are similar. It is a matter of thrust with the vendor.</p>
<h2 id="heading-how-to-deal-with-recovery-when-using-hardware-bound-credentials">How to deal with recovery when using hardware-bound credentials?</h2>
<p>A device can be lost, broken, or stolen. You must deal with it. The most straightforward way is to offer the user a way to register multiple passkeys, so that losing one device does not imply locking oneself out.</p>
<p>Another alternative is to provide a recovery procedure per SMS, TOTP or some other thrusted means. Relying on solely a password as recovery is discouraged, since the recovery per password then becomes the "weakest link" of the authentication system.</p>
<h2 id="heading-discoverable-vs-non-discoverable">Discoverable vs non-discoverable?</h2>
<p>There are two ways to trigger authentication. By providing a list of allowed credential ids for the user or not.</p>
<p>If no list is provided, the default, an OS native popup will appear to let the user pick a passkey. One of the <em>discoverable</em> credential registered for the website. However, if the credential is <em>not discoverable</em>, it will not be listed.</p>
<p>Another way is to first prompt the user for its username, then the list of allowed credential IDs for this user from the server. Then, calling the authentication with <code>allowCredentials: [...]</code>. This usually avoids a native popup and goes straight to user verification.</p>
<blockquote>
<p>There is also another indirect consequence for "security keys" (USB sticks like a Yubikey). Discoverable credentials need the ability to be listed, and as such require some storage on the security key, also named a "slot", which are typically fairly limited. On the other hand, non-discoverable credential do not need such storage, so unlimited non-discoverable keys can be used. There is an interesting article about it <a target="_blank" href="https://fy.blackhats.net.au/blog/2023-02-02-how-hype-will-turn-your-security-key-into-junk/">here</a>.</p>
</blockquote>
<h2 id="heading-can-i-know-if-a-passkey-is-already-registered">Can I know if a passkey is already registered?</h2>
<p>No, the underlying WebAuthn protocol does not support it.</p>
<blockquote>
<p>A request to add an <code>exists()</code> method to guide user experience has been brought up by me, but was ignored so far. See <a target="_blank" href="https://github.com/w3c/webauthn/issues/1749">issue #1749</a> (and voice your opinion!).</p>
</blockquote>
<p>As an alternative to the problem of not being able to detect the existence of passkeys, major vendors pushed for an alternative called "conditional UI" which in turn pushes discoverable synced credentials.</p>
<h2 id="heading-what-is-conditional-ui-and-mediation">What is conditional UI and mediation?</h2>
<p>This mechanism leverages the browser's input field autocomplete feature to provide public key credentials in the list. Instead of invoking the WebAuthn authentication on a button click directly, it will be called when loading the page with "conditional mediation". That way, the credential selection and user verification will be triggered when the user selects an entry in the input field autocomplete.</p>
<blockquote>
<p>Note that the input field <em>must</em> have <code>autocomplete="username webauthn"</code> to work.</p>
</blockquote>
<h2 id="heading-what-is-attestation">What is attestation?</h2>
<p>The attestation is a proof of the authenticator model.</p>
<blockquote>
<p>Note that several platforms and password managers do not provide this information. Moreover, some browsers allow replacing it with a generic attestation to increase privacy.</p>
</blockquote>
<h2 id="heading-do-i-need-attestation">Do I need attestation?</h2>
<p>Unless you have stringent security requirements where you want only specific hardware devices to be allowed, you won't need it. Furthermore, the UX is deteriorated because the user first creates the credential client-side, which is then rejected server-side.</p>
<blockquote>
<p>There was a feature request sent to the RFC to allow/exlude authenticators in the registration call, but it never landed in the specs.</p>
</blockquote>
<h2 id="heading-usernameless-authentication">Usernameless authentication?</h2>
<p>While it is in theory possible, it faces a very practical issue: how do you identify the credential ID to be used? Browsers do not allow having a unique identifier for the device, it would be a privacy issue. Also, things like local storage or cookies could be cleared at any moment. But <em>if</em> you have a way to identify the user, in a way or another, then you can also deduct the credential ID and trigger the authentication flow directly.</p>
<h2 id="heading-what-about-the-security-aspects">What about the security aspects?</h2>
<p>The security aspects are vastly different depending on:</p>
<ol>
<li><p>Synced or hardware-bound</p>
</li>
<li><p>User verification or not</p>
</li>
<li><p>Discoverable or not</p>
</li>
</ol>
<p>A hardware-bound key is a "factor", since you have to possess the device. The other factor would be "user verification", since it is something that you know (device PIN or password) or are (biometrics like fingerprint).</p>
<p>Many implementations favor <em>synced credentials with optional user verification</em> though, for the sake of <em>convinience</em>, combined with discoverable credentials. This is even the default in the WebAuthn protocol and what many guides recommend.</p>
<p>In that case, the security guarantee becomes: <em>"the user has access to the software authenticator account"</em>. It's a delegated guarantee. It is obvious that having the software authenticator compromised (platform account or password manager), would leak all passkeys since they are synced.</p>
<h2 id="heading-what-about-privacy-aspects">What about privacy aspects?</h2>
<p>Well, if the passkeys are synced, it's like handing over the keys to your buddy, the software authenticator, in good faith. That's all. If the software authenticator has bad intents, gets hacked or the NSA/police knocks on their door, your keys may be given over.</p>
<blockquote>
<p>Note that if a password manager has an "account recovery" or "sharing" feature, it also means it is able to decrypt your (hopefully encrypted) keys / passwords. On the opposite, password managers without recovery feature usually encrypt your data with your main password. This is the more secure/private option, since that way, even they cannot decrypt your data.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Snail-paced development]]></title><description><![CDATA[The situation
Passwordless.ID isn't progressing lately. While I would love to have Passwordless.ID already done and wrapped up, it's still far from it.

The reason: lack of time, lack of resources.

It's a personal project. It's not some big company ...]]></description><link>https://blog.passwordless.id/snail-paced-development</link><guid isPermaLink="true">https://blog.passwordless.id/snail-paced-development</guid><category><![CDATA[devlog]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Tue, 30 Apr 2024 07:11:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/OxK32aLJXWU/upload/cf3037647f5098f8373997b289423611.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-situation">The situation</h2>
<p>Passwordless.ID isn't progressing lately. While I would love to have Passwordless.ID already done and wrapped up, it's still far from it.</p>
<blockquote>
<p>The reason: lack of time, lack of resources.</p>
</blockquote>
<p>It's a personal project. It's not some big company or big bucks behind this. It's just an idealistic me, cursing at the utterly complex always forgotten passwords as a user and cursing at OAuth2 providers for the incurred complexity you have to deal with as a developer. Not to mention the account breaches due to insufficient precautions. With the recent WebAuthn/Passkeys protocol, started my journey to simplify all this. Make things simple, secure and "passwordless" for everyone. That was the goal (and still is).</p>
<p>However, as I mentioned before, it progresses at a snails pace. I sometimes work on it when commuting in the train, sometimes an hour in the evening or during the week-end. But this is not very productive. What should take hours take days, what should take days take weeks, what should take weeks take months. And it all overlaps with my personal time, which sometimes feels pleasant, sometimes feels like a burden, resulting in development even more spread over time.</p>
<p>And this feels quite annoying in itself. When developing in such a way, small piece after small piece, everything takes so long. You don't have this burst of productive work with satisfaction that a big chunk is done. It's like watching a snail sloooowly move without feeling any substantial progress.</p>
<p>It's ready to be overtaken by other "authentication solutions". Or rather, lagging behind the whole time. But who knows, there is a saying "slow and steady wins the race", and that's exactly what I'm doing now. I still work on it every week and small habits also get things done over a long time.</p>
<h2 id="heading-the-solution-attempt">The solution attempt</h2>
<blockquote>
<p>There is only one way to put this on the fast track: time &amp; money.</p>
</blockquote>
<p>My current workplace did not support this project. It costs my precious working time and does not earn money. In other words, it's not a profitable endeavour for the company. I can't blame them. The other reason is a bit more subtle. The top brass prefers to use "big brands" for authentication rather than some yet unknown project. This is another ice difficult to break through. But I'll talk to them again, to see if there is a way I could spend some time on this.</p>
<p>Alternatively, I also recently applied to "<a target="_blank" href="https://prototypefund.de/">Prototype.Fund</a>" which is some funding initiative for open source projects in Germany. While there is no guarantee to be selected, I do hope it goes through as it would be a silver-lining to get started. At least, I could develop freely without digging in my employer's pockets. My major project at work should also be settled by the time the funding start, so the timing is right. We'll see, let's cross the fingers.</p>
<p>I even considered pushing all my cards into it, resigning and working full time on it. However, this is too risky for me. I got family and need financial stability, it's too big of a gamble. Not to mention that earning a stable income with a free public service is challenging.</p>
]]></content:encoded></item><item><title><![CDATA[Passkeys library, now with authenticator icons]]></title><description><![CDATA[Hello folks!
Since I have little time to cater about Passwordless.ID lately, I wanted to at publish some little update. So here we go, the WebAuthn library (to enable passwordless login using passkeys) now also delivers more information about the "au...]]></description><link>https://blog.passwordless.id/passkeys-library-now-with-authenticator-icons</link><guid isPermaLink="true">https://blog.passwordless.id/passkeys-library-now-with-authenticator-icons</guid><category><![CDATA[#webauthn]]></category><category><![CDATA[passkeys]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Mon, 26 Feb 2024 21:02:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1708965812563/45cb9af3-8006-4774-87be-b3199ba83a56.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello folks!</p>
<p>Since I have little time to cater about <a target="_blank" href="https://passwordless.id/">Passwordless.ID</a> lately, I wanted to at publish some little update. So here we go, the <a target="_blank" href="https://webauthn.passwordless.id/">WebAuthn library</a> (to enable passwordless login using passkeys) now also delivers more information about the "authenticator". In particular the icon and whether it is a multi-device or device-bound credential. (The latter one is actually just interpreting the existing flags)</p>
<p>👇 Check out the <a target="_blank" href="https://webauthn.passwordless.id/demos/basic.html">simple demo</a> if you want.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708965557829/0ae8c2f3-84ee-454c-a3ab-9b0321c5f599.png" alt class="image--center mx-auto" /></p>
<p>👇 Or the list of <a target="_blank" href="https://webauthn.passwordless.id/demos/authenticators.html">authenticators</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708965618988/aa3ff3ec-5770-46d6-aef8-c81d5fb7e6a4.png" alt class="image--center mx-auto" /></p>
<p>👇 Or the <a target="_blank" href="https://webauthn.passwordless.id/demos/playground.html">playground</a> to experiment with the various options.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708965673392/a056b457-be2e-4e02-8eff-3fc30b348230.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>⚠️ As a side note, please do not forget that the challenge should be randomly generated server side! I have found repositories on GitHub using hardcoded challenges, which is of course a red flag regarding security since it opens the way to replay attacks!</p>
</blockquote>
<p>Read <a target="_blank" href="https://passwordless.id/protocols/webauthn/1_introduction">here</a> or <a target="_blank" href="https://webauthn.passwordless.id/">here</a> if you want a brief introduction of how the WebAuthn protocol behind Passkeys works.</p>
<hr />
<p>Starting from version 1.4.0+, the parsed registration payload now looks as follows, with extra properties <code>synced</code>, <code>icon_light</code> and <code>icon_dark</code>.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"username"</span>: <span class="hljs-string">"Arnaud"</span>,
  <span class="hljs-attr">"credential"</span>: {...},
  <span class="hljs-attr">"client"</span>: {...},
  <span class="hljs-attr">"authenticator"</span>: {
    <span class="hljs-attr">"rpIdHash"</span>: <span class="hljs-string">"T7IIVvJKaufa_CeBCQrIR3rm4r0HJmAjbMYUxvt8LqA="</span>,
    <span class="hljs-attr">"flags"</span>: {...},
    <span class="hljs-attr">"counter"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">"synced"</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">"aaguid"</span>: <span class="hljs-string">"08987058-cadc-4b81-b6e1-30de50dcbe96"</span>,
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Windows Hello"</span>,
    <span class="hljs-attr">"icon_light"</span>: <span class="hljs-string">"https://webauthn.passwordless.id/authenticators/08987058-cadc-4b81-b6e1-30de50dcbe96-light.png"</span>,
    <span class="hljs-attr">"icon_dark"</span>: <span class="hljs-string">"https://webauthn.passwordless.id/authenticators/08987058-cadc-4b81-b6e1-30de50dcbe96-dark.png"</span>
  },
  <span class="hljs-attr">"attestation"</span>: <span class="hljs-string">"o2NmbXRjdHBtZ2F0dFN0bXSmY2FsZzn__mNzaWdZAQB25KbRrQPjtlx0qZ2Tsvh2YHaPTPTUJznShhr5XnP3zBmVv..."</span>
}
</code></pre>
<p>The icons are deliberately made as links to keep the library compact. Otherwise, it would be bloated way beyond the megabyte. Instead, all icons are available as links.</p>
<p>These authenticator icons were made thanks to this <a target="_blank" href="https://github.com/passkeydeveloper/passkey-authenticator-aaguids">repository</a> which contains the icons as data urls, sometimes in PNG, sometimes in SVG, sometimes square, sometimes rectangle, some tiny, some really large, etc. So some extra scripting was done to homogenize them as 64x64 PNGs and host them so that each "aaguid" can be shown through a direct link.</p>
<hr />
<p>Thanks for reading. If you like it, leaving a comment or a <a target="_blank" href="https://github.com/passwordless-id/webauthn">star</a> is appreciated. Getting some feedback is always nice.</p>
<p>Have a nice day!</p>
]]></content:encoded></item><item><title><![CDATA[How security and privacy impacts the database design]]></title><description><![CDATA[What I like about the banner picture above is that it is a very useful analogy to our problem at hand. A database is nothing else than a glorified electronic version of it. Lots of small storage boxes containing some data, and identified by a label o...]]></description><link>https://blog.passwordless.id/how-security-and-privacy-impacts-the-database-design</link><guid isPermaLink="true">https://blog.passwordless.id/how-security-and-privacy-impacts-the-database-design</guid><category><![CDATA[Security]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Design]]></category><category><![CDATA[privacy]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Tue, 05 Dec 2023 07:49:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/lRoX0shwjUQ/upload/665ed0c47529ae4cd603d4dae9f2c030.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>What I like about the banner picture above is that it is a very useful analogy to our problem at hand. A database is nothing else than a glorified electronic version of it. Lots of small storage boxes containing some data, and identified by a label or number, usually called "primary key".</p>
<p>In security, the main concern is protecting against breaches altogether. However, it is also a good idea to think about mitigating impact in case of breaches. Assuming we deal with some kind of sensitive user data, let's have some thoughts on what should be on the labels and the content of these wooden boxes.</p>
<h2 id="heading-what-should-be-the-user-identifier">What should be the user "identifier"?</h2>
<p>Should it be the e-mail? Or a username? ...</p>
<blockquote>
<p>TL;DR: No, it should be an anonymous ID.</p>
</blockquote>
<p>How to <em>identify</em> users is something to be decided at the very beginning. It's crucial information that spreads everywhere: in the database, in links, in software logic... Soon, everything will be tied to it and too late to change. Doing so will be extremely difficult in the best case, or impossible in the worst case.</p>
<p>But why should it be an anonymous ID? ...It's not only about data breaches.</p>
<p>That ID is probably going to show up in extern systems too. In URLs, in logs, in databases, sent to third-party services... So, in the grand scheme of things, privacy-wise, it's better to be anonymized.</p>
<p>Security-wise, using a username/e-mail as the primary ID is not a vulnerability by itself. However, it slightly increases the "attack surface". For example, imagine you have a REST API with some endpoints accidentally not properly controlling access. If they accept a username/e-mail as parameter, it's trivial to invoke these endpoints with other user's username/e-mail as input, since these are fairly easy to find out. On the other hand, using anonymized IDs would already make exploiting such a vulnerability more difficult, since you don't know other's ID in the first place. This not only applies for REST API, but in all kinds of hacking, whether it's digging in a stolen file or eavesdropping traffic. It does not make your system invulnerable, but it's an additional layer of safety.</p>
<p>Imagine again the wooden boxes above, just don't place names on it for everyone to see but use some other identifier instead. For example, use the SHA256 of the username/e-mail with a salt. That's simple, deterministic and anonymous.</p>
<h2 id="heading-what-about-the-stored-data">What about the stored data?</h2>
<p>Most of the time, plain data is read and written directly to the database. The connection is typically protected by credentials to restrict access. This is all good and fine but the main access credentials become critical. If the access credentials are leaked or stolen, all the data can be extracted. Plain and simple.</p>
<p>Depending on the sensitivity of the data, like for example personal or financial data, another layer of protection is required: encrypting the data. Instead of placing the original text sheet in the wooden box, you encrypt it beforehand. Reading and writing encrypted data in the database makes it unreadable to others. Even database administrators would not be able to pry into the stored data, nor any other software not in possession of the encryption key. Likewise, this also increases privacy.</p>
<p>Note that although most databases also feature some encryption mechanisms, it typically refers to the raw data persisted on disk. To make the database storage files unreadable when stolen. These should not be mixed up and ideally, both should be used.</p>
<blockquote>
<p>TL;DR: always encrypt all personal, financial or otherwise sensitive information when storing it in a database</p>
</blockquote>
<h2 id="heading-hash-what-you-can">Hash what you can!</h2>
<p>Not only passwords but also recovery codes and other nonces.</p>
<p>As an anecdote, it reminds me of a story about a hacker who found an SQL injection vulnerability, enabling him to pry into the database. Of course, the user passwords were hashed. Even the sensitive data was encrypted. On the other hand, the recovery codes were stored "as is". The hacker now just had to start a recovery procedure for an arbitrary user account, and then look up the corresponding plain text recovery code. Bam! The hacker could now simply complete the recovery procedure and reset the password for any user account. That escalated quickly.</p>
<p>So if you don't really <em>need</em> the plain text data, don't even store it, just a hash of it.</p>
<blockquote>
<p>TL;DR: hash not only passwords but also recovery codes, nonces, challenges and such.</p>
</blockquote>
<h2 id="heading-pairwise-identifiers">Pairwise identifiers</h2>
<p>User accounts are usually identified by some sort of ID. It might be a hash, a UUID, a username, an e-mail, whatever. When some third-party interacts with your service, it'll likely use this ID to identify the user or resource.</p>
<p>This also means that this ID is "universal" and that everyone out there will use this ID as an identifier. A user can be tracked that way, and attack attempts can be carried out that way using the known ID from another party.</p>
<p>A concept to push privacy and security further is called "pairwise identifiers". Each "consumer" of your service will be provided with different user identifiers.</p>
<blockquote>
<p>Third-party service XYZ: can you please send me data related to user ABC123 ?</p>
<p>Provider: sure ...let me check ...for you XYZ, the account ABC123 is mapped to "Bob" ...here you go.</p>
</blockquote>
<p>Of course, "Bob" is not the username, but should be the internal primary id.</p>
<p>It's about providing a mapping so that each third-party sees different IDs, so that they cannot correlate users among themselves. Also, it prevents leaked IDs from being used by others. As usual, these benefits both privacy and security.</p>
<blockquote>
<p>TL;DR: it's best to provide third-party services anonymized IDs, unique third-party for each third-party. Both for privacy and security.</p>
</blockquote>
<h2 id="heading-good-ol-cookies">Good ol' cookies</h2>
<p>This last advice is not directly related to the database but rather to how to maintain browser sessions. There are two camps: "use a good ol' session ID cookie" vs "use a JSON Web Token" (In an Authorization header or as cookie). The latter sounds more modern and trendy but it has drawbacks regarding security.</p>
<p>Let's review the good old way of handling sessions first. It's fairly straightforward: set a "Http-Only" cookie with some random session identifier when the user is authenticated. Voila, done. The browser will automatically send the cookie on each subsequent request and it cannot be read nor written by scripts. Server-side, you can retrieve the session data based on that id. Then, when the user signs out you can remove the cookie and clear your session data. Same if the user is inactive for too long. Simple, effective and there is not much that can go wrong.</p>
<p>Regarding the JWT tokens, there are two dangers with it. First, if the token which is typically stored browser side is stolen, the attacker can impersonate the user, even long after the user signed out... Except if you keep a database of revoked tokens, which not only loses the main benefit of JWT being "stateless" but also adds undesired complexity.</p>
<p>The second danger is the signing key being stolen, whether it's because of an accident, a vulnerability exploit or a malicious insider. Although this is unlikely because the key should be well protected, the potential consequences of a breach are catastrophic. Basically, your service would be doomed overnight because attackers would be able to impersonate any user at will, bypassing authentication altogether. This is a worst-case scenario that would not be possible for sessions identified by random IDs.</p>
<blockquote>
<p>TL;DR: prefer random identifiers in a Http-Only cookie over using JWT tokens for user sessions</p>
</blockquote>
<h2 id="heading-doing-it-later-is-no-good">Doing it "later" is no good</h2>
<p>It might be tempting to delay it, since it introduces complexity to the codebase. However, it's no good. The more you delay it, the more effort and time will be consumed in a later stage refactoring anyway. But even more importantly, while some changes <em>can</em> be delayed, others would break the API and data compatibility for all "consuming" software!</p>
<p>In particular, everything regarding hashed user IDs and pairwise identifiers will be breaking all software and integrations relying on these IDs. Changing these is a "hard cut" that should be done ASAP early on, ideally during the conception phase.</p>
<p>Other changes have a less critical impact. For example, hashing of short-lived recovery codes, nonces, challenges, etc. can be done in a version update. This will invalidate existing codes, nonces, and challenges and cause a small disturbance, but everything will work fine again afterwards.</p>
<p>The only change which is delayable with less concern is the data encryption. This is an internal database change, opaque to the API which remains unchanged and the consumers as well. It can be done in one fell swoop by encrypting all the data in the database at once using a background process.</p>
<blockquote>
<p>TL;DR: don't delay it, do it ASAP since it's breaking changes</p>
</blockquote>
<h2 id="heading-side-benefits">Side benefits</h2>
<p>Following these recommendations will increase the security and privacy of your system, but that's not all. Doing this, although it sounds complex, has its benefits too regarding code structure. <em>It forces the software to access user data through a streamlined access point instead of fetching it directly from the database</em>. When all calls for user data go through this access point, it's also easier to monitor, control access and properly handle the data in this one place.</p>
<p>At least, that's what we experienced after our "<a target="_blank" href="https://blog.passwordless.id/endless-refactoring">endless refactoring</a>" at <a target="_blank" href="https://passwordless.id">Passwordless.ID</a>. The refactoring is large, and the "version 2" breaks compatibility, requiring us to deploy a separate version and clear all user accounts. It is a <em>very</em> hard cut. However, we are pleased with the result. The system is now more secure, with better privacy, and better structured than ever before. Something I am proud of!</p>
<blockquote>
<p>TL;DR: it will even make your codebase structure cleaner!</p>
</blockquote>
<p>Thanks for reading! If you liked it, leave a comment!</p>
]]></content:encoded></item><item><title><![CDATA[Endless refactoring]]></title><description><![CDATA[Refactoring is a necessary thing. The reasons are numerous, but most of them have one thing in common: they are underestimated. As the old adage says: "the devil lies in the details". And typically these "details" surface during refactoring itself, l...]]></description><link>https://blog.passwordless.id/endless-refactoring</link><guid isPermaLink="true">https://blog.passwordless.id/endless-refactoring</guid><category><![CDATA[refactoring]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Tue, 14 Nov 2023 08:48:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/npxXWgQ33ZQ/upload/cbda89562f85839fc9cc624624d61385.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Refactoring is a necessary thing. The reasons are numerous, but most of them have one thing in common: they are underestimated. As the old adage says: "the devil lies in the details". And typically these "details" surface during refactoring itself, leading to even more stuff to refactor, sometimes creeping up to dangerous levels.</p>
<p>This is a tale of such refactoring for <a target="_blank" href="https://passwordless.id">Passwordless.ID</a>, or rather how a large refactoring was aborded in the middle in favour of an intermediate solution.</p>
<p>Well, it turns out the refactoring was closer to a whole rewrite. But before delving into the details, let's check the "why" it was done, since the goals are what drive such refactoring.</p>
<h2 id="heading-why">Why?</h2>
<blockquote>
<p>The big refactoring started to take place, mainly driven by a follow-up of <a target="_blank" href="https://blog.passwordless.id/ui-api-db-pushing-three-tier-architecture-too-far">UI, API, DB: pushing "three-tier architecture" too far?</a>.</p>
</blockquote>
<p>Before, front-end and back-end were in two separate repositories, with their own lifecycle. However, from a workflow perspective, <strong>having both the API and UI in the same repository is more convenient</strong>. Changes always go hand-in-hand, you have a single URL for previews, no <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> necessary, a single pipeline and deployment, etc. The code is almost the same, it's just more convenient to work with a single repository.</p>
<p>The second goal was <strong>improving user experience, and specifically reducing "time-to-rendering" as much as possible</strong>. Currently, this was slowed down by:</p>
<ul>
<li><p>130kb assets loading due to frontend framework (not that much, but way larger than it could be)</p>
</li>
<li><p>"Waterfall loading" (load initial assets, run script, fetch dynamic content, load other stuff, render it all ...duh, feels sluggish)</p>
</li>
<li><p>Cross-origin requests (adds one more "<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request">preflight</a>" HTTP round-trip)</p>
</li>
</ul>
<p>The combination of these things resulted in rendering times above a second when not cached, sometimes even two or three if the CDN caches also missed. In other words, it was slow despite being a super simple page.</p>
<p>Lastly, some <strong>structural changes to improve security and privacy</strong> would require changes on the data level. This includes changing the primary ID from the username to a hash and another round of encryption for the stored data. This would break the compatibility with existing data. Please note though that the privacy and security of the existing version is by no means compromised. This is about being paranoid and security/privacy in-depth by adding one more protection layer.</p>
<h2 id="heading-how">How?</h2>
<p>There were three main areas related to the refactoring.</p>
<ol>
<li><p>Combining both codebases with same domain previews</p>
</li>
<li><p>Making the UI more lightweight</p>
</li>
<li><p>Adapting the data layer</p>
</li>
</ol>
<p>These are just a few sentences but represent lots of work. In particular, the UI refactoring. Making this more lightweight is easier said than done. Investigating alternatives and shifting away from the larger Vue framework turned out to be quite the burden.</p>
<h2 id="heading-the-mistake-made">The mistake made</h2>
<p>We proceeded as follows</p>
<ul>
<li><p>Merge both codebases</p>
</li>
<li><p>Refactor the backend code while we are at it 🙄</p>
</li>
<li><p>Investigate frontend framework alternatives 😅</p>
</li>
<li><p>Try out various frontend techs😬</p>
</li>
<li><p>Start porting UI🥵</p>
</li>
</ul>
<p>What started as a refactoring was turning more and more in the direction of a full rewrite.</p>
<p>The refactoring of the backend code was a bit time-consuming but worth it IMHO. It's slightly more lightweight and forcing a proper file structure is a good thing. It makes the code more organized and neater. You can directly pinpoint API endpoints to source code files and the related middleware without even looking at the code.</p>
<p>On the other hand, exploring the vast space of technologies available to replace the front-end was a bottomless pit. Even the first steps of refactoring the UI became a huge time sink. It's not only comparing the few major frameworks, it's even broader, related to the methodology: ranging from SPA (single-page-applications), to SSR (server-side rendering), to SSG (server-side generation), to bundling tools for plain HTML/TS/JS/CSS. Each of these methodologies has its own ecosystem and tools, with many frameworks to compare. Last but not least, many frameworks are often hybrids and can be "configured" differently to span a range of SPA/SRR/SSG.</p>
<p>In retrospect, it was a big mistake. It slowly but surely evolved into a complete rewrite. The UI refactoring is a huge endeavour that should have been left untouched and made separately. <em>Not because it should not be done, but because it should not have been done</em> <strong><em>now</em></strong>.</p>
<h2 id="heading-how-it-should-have-proceeded">How it should have proceeded</h2>
<p>The whole order in which we started the "big refactoring" was wrong. Of course, it wasn't originally expected to be that time-consuming either, it was just "let's refactor that". In hindsight, we should have paid much more attention to the priorities and plan the refactoring accordingly. It should have been done in smaller steps, one after another, and in an order that makes sense according to the priorities.</p>
<p>In particular, the UI-related one should have been done last. Of course, from a marketing and business point of view, one might disagree. The holy UI/UX would take priority, with the slogan "make it nice first, with a slicker UI which loads super fast". Something that "looks awesome" ...but that would hide and delay upcoming breaking changes, which would just frustrate early adopters later on. I'm against these kinds of short-term wins at the price of long-term liabilities. The sooner the breaking change is done, the better. That is also why any kind of promotion is on hold until this time-consuming refactoring is completed.</p>
<p>What should have been prioritized is merging both codebases into one for single domain deployment, data schema refactoring to use hashes as IDs and one more encryption layer. The reason to handle them first is because it's a breaking change. These two things make it incompatible with the original first version.</p>
<ul>
<li><p>Merge both codebases</p>
</li>
<li><p>Ensure single domain dev previews work fine</p>
</li>
<li><p>Apply schema changes for hashed IDs</p>
</li>
<li><p>Add extra encryption round</p>
</li>
</ul>
<p>Once this is done, the new version can be published. And afterwards, the UI and back-end related "improvements" can take place. Those which take more time but keep compatibility.</p>
<h2 id="heading-to-abord-or-to-persevere">To abord or to persevere?</h2>
<p>We're now stuck "in the middle" of the rewrite including the new UI, so it's kind of annoying to drop the work in progress halfway. On the other hand, it looks like the road ahead is still quite long. So, despite it's annoying to let the work in progress halfway done, it's better for Passwordless.ID as a whole. The sooner the "v2" is released, the better.</p>
<p>Also, what we did not investigate was how to make the existing UI lighter. We aren't talking about excessive amounts here, it's ~130kb gzipped JS/CSS over the wire. But for a simple sign in/up page, it's still unnecessarily large. After some experimenting, it turns out that ~100kb can be spared, just by dropping the <a target="_blank" href="https://github.com/bootstrap-vue-next/bootstrap-vue-next">bootstrap-vue-next</a> lib in favour of using plain bootstrap classes directly and a few workarounds for special components. That the lib came in with that much "baggage" and could not be properly "tree-shaken" came unsuspected. Dropping it, the page goes from ~130kb =&gt; ~30kb, already much nicer.</p>
<p>The full UI rewrite will still take place later on, to be even more efficient and internationalized. But for now, we will make the bare minimum UI changes and focus on the breaking change first, which will hopefully be completed sooner.</p>
<p>Stay tuned!</p>
]]></content:encoded></item><item><title><![CDATA[UI, API, DB: pushing "three-tier architecture" too far? 🤔]]></title><description><![CDATA[What may look ideal in theory, may turn out cumbersome in practice.
-- Myself

During inception, the Passwordless.ID "app" was built in the purest form of a three-tier architecture.

The UI - A vue app "compiled" into a single-page-application

The A...]]></description><link>https://blog.passwordless.id/ui-api-db-pushing-three-tier-architecture-too-far</link><guid isPermaLink="true">https://blog.passwordless.id/ui-api-db-pushing-three-tier-architecture-too-far</guid><category><![CDATA[software architecture]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Sun, 13 Aug 2023 07:54:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/m9qMoh-scfE/upload/269a7c7b50e2a3507432bd195386c00a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>What may look ideal in theory, may turn out cumbersome in practice.</p>
<p>-- Myself</p>
</blockquote>
<p>During inception, the <a target="_blank" href="https://passwordless.id">Passwordless.ID</a> "app" was built in the purest form of a three-tier architecture.</p>
<ul>
<li><p>The UI - A vue app "compiled" into a single-page-application</p>
</li>
<li><p>The API - An API built with Cloudflare Workers</p>
</li>
<li><p>The DB - A distributed DB as a service</p>
</li>
</ul>
<p>In particular, each "tier" was completely independent, built with its own tech stack and deployed on a dedicated subdomain.</p>
<ul>
<li><p>The UI: <a target="_blank" href="https://ui.passwordless.id">https://ui.passwordless.id</a></p>
</li>
<li><p>The API: <a target="_blank" href="https://api.passwordless.id">https://api.passwordless.id</a></p>
</li>
<li><p>The DB: internal network</p>
</li>
</ul>
<p>This complete separation of "tiers" may look ideal. It seems to be full of advantages.</p>
<ul>
<li><p>There is a clear separation of concerns</p>
</li>
<li><p>Each tier can be updated independently</p>
</li>
<li><p>One could make changes to the UI without affecting the API and vice-versa.</p>
</li>
<li><p>They can scale independently</p>
</li>
<li><p>The UI is fully browser cached</p>
</li>
<li><p>Each "tier" (UI, API, DB) could theoretically be swapped out with another tech in the long term...</p>
</li>
</ul>
<p>This might seem like an exemplary separation of concerns, something ideal to strive for. It's also what we strived for during inception. Sounds great right? Well, it turns out it's not that great ...it's actually pretty bad.</p>
<h2 id="heading-decoupling-back-end-and-front-end-sucks">Decoupling back-end and front-end sucks</h2>
<p>Being able to update and make changes to UI and API independently sounds great, but in practice, it turns out it's very rarely needed.</p>
<p>The vast majority of the time, when you work on something, whether it is a new feature, a change or a bug, you typically update mostly the UI and API hand in hand. You change files in both, test both together, and deploy both.</p>
<p>In the daily routine, having two repositories, toolchains and domains to deploy to turns out to be counter-productive. It'll lead to two commits, two builds, a "joint deploy", etc. It's not that big of a deal either, it's just annoying.</p>
<h2 id="heading-cross-origin-requests-suck">Cross-origin requests suck</h2>
<p>Due to the UI and API being on two distinct subdomains, requests between the UI and the API are now "cross-origin requests". It applies to different subdomains too. Configuring the API to allow such "cross-origin requests" is no big deal when you know how it works, but some developers may find it cumbersome.</p>
<p>The subtle disadvantage is that it introduces one more round-trip: the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request">Preflight requests</a>. These requests are automatically sent by the browser to check if the requests from UI to API are allowed, before sending the actual requests. While not dramatic, it makes the UI slightly less responsive since it doubles the first request's latency.</p>
<p>Lastly, it also has an impact on session handling and security aspects due to its cross-origin nature. However, that's a whole other topic itself.</p>
<h2 id="heading-latency-sucks">Latency sucks</h2>
<p>You probably know it, when you develop locally, everything is snappy. The page loads instantly and you are happy ...and once it's deployed, you notice that the experience on your phone in "real life" is not that great, especially before all the browser caching kicks in.</p>
<p>The slowness comes from various things:</p>
<ul>
<li><p>the loading of assets from the SPA</p>
</li>
<li><p>the UI pre-flight requests to the API</p>
</li>
<li><p>the UI actual requests to the API</p>
</li>
<li><p>the API calls to the distributed DB</p>
</li>
<li><p>the "rendering" of the page content</p>
</li>
</ul>
<p>Each of these things makes the UI sluggish and is a consequence of the distinct tiers being fully separated. To be more precise, it is because network calls are involved between all parts, each one increasing the latency and sluggishness by a notch.</p>
<p>Moreover, you typically don't notice this "sneaky" behaviour during the initial phase of development. When testing locally, everything appears lightning fast since everything occurs locally without network latency. Often, the sluggishness introduced by the network calls is only discovered when the first prototype is deployed and going "live".</p>
<p>That is why an application which combines everything locally, or in a same subnet, is usually much more responsive than having distinct "tiers" like UI / API / DB each separated by a network, which is common in a SaaS world.</p>
<h2 id="heading-distinct-subdomains-suck">Distinct subdomains suck</h2>
<p>Whether you want to show your users a feature preview, provide a developer sandbox, make A/B testing or reproduce some bug in real-life conditions, "staging" environments are always useful.</p>
<p>If both the UI and API are packaged in the same app, deploying it at a single domain like <em>https://prod.passwordless.id</em> would be straightforward. Then, you could also work on a feature branch and deploy it to <em>https://new-feature.passwordless.id</em> to test it out in a live environment.</p>
<p>However, this becomes much more complex if you have it split. It would become something like this:</p>
<ul>
<li><p><em>https://ui.new-feature.passwordless.id</em></p>
</li>
<li><p><em>https://api.new-feature.passwordless.id</em></p>
</li>
</ul>
<p>It also requires some plumbing, so that the new feature UI talks with the corresponding API URLs, including adjusting CORS properly. This is extra work and is error-prone.</p>
<p>If the UI and API were bundled together in the same (sub)domain, that would not be a problem since relative URLs could simply be used and CORS are not involved either.</p>
<h2 id="heading-spas-sometimes-suck">SPAs (sometimes) suck</h2>
<p>SPAs like Vue, React or Angular are not bad. You have plenty of libraries with all kinds of widgets and fancy stuff. You can just "magically" quickly generate whole apps with some initializer. But it has a cost too: the learning curve, the complex toolchain, the clunky dependencies ...and the initial page load time due to larger size and rendering delays.</p>
<p>It's a tradeoff. While SPAs typically have a longer initial loading time, they offer complex widgets and increased interactivity in return. It offers ways to structure complex web applications in a modular way to keep their complexity under control. All these things are great ...if you need them. Otherwise, when you just need a few basic pages, it would likely turn into useless overhead.</p>
<p>In the end, whether SPAs "make sense" totally depends on the app. The more complex and user interaction-heavy the app is, the better suited it will be for SPAs. However, in the case for Passwordless.ID, which has a relatively simple UI, it was counter-productive.</p>
<p>Doing it as a Vue SPA was great to get started quickly, but in the meantime, it hinders me more than benefits me. The UI library used was bug-ridden, the various toolchains between UI, API and deployment platform do not always play well together, the resulting bundle is 400kb big and it'd cost time and effort to reduce it and the bad resulting latency is the nail in the coffin. Good ol' HTML ain't that bad after all.</p>
<h2 id="heading-back-to-the-basics">Back to the basics</h2>
<p>Lately, there is a renaissance of good ol' server-side templating. The basics are making a comeback under two umbrella names: SSR (Server-side rendering) and SSG (Server-side generation). SSG means templating at build time, like "generating" pages in various languages, while SSR means templating with dynamic data, like showing the result of a database query. Both have their roles and are complementary.</p>
<p>It's just going back to the forgotten roots of the web, noticing that after all, it's quite handy to produce HTML with the right data inside directly. It's simple, fast and "slim". This is a contrast to the SPAs which typically inflate, requiring larger JS assets, fetch the data in a second step and add rendering delays.</p>
<p>Sadly, the ecosystem is very fragmented in this area.</p>
<h2 id="heading-porting-software-sucks">Porting software sucks</h2>
<p>It would be foolish to leave the "architecture" decision purely to theoretical arguments. Moreover, it usually involves switching or at least adapting the technology stack. As such, its ecosystem plays a crucial role. If you go against the "intended usage" of your tech stack, you may fight an uphill battle not worth it.</p>
<p>In particular, at the time of Passwordless.ID's inception in mid-2022, Cloudflare pages <em>functions</em> simply did not exist yet. As such, the option to package both (with Cloudflare) was not even possible at that point. Pages <em>functions</em> appeared later, by the end of 2022. It certainly would have been able to use a more traditional technology stack at that time. However, the deployment and scalability comfort from a Cloudflare Pages/Workers combo was what pulled us over. It was a pragmatic choice rather than the ideal tech stack.</p>
<p>The point is that it would <strong><em>now</em></strong> be possible to combine the back-end and front-end in a single codebase. Is it worth porting the existing codebase? Tough question. I'd say "probably" but it's a substantial effort. The main issue is that in our case, the whole ecosystem around Cloudflare pages <em>functions</em> is very young. It lacks tooling, libraries, Q&amp;A, documentation and so on. It is a "bleeding edge" right now.</p>
<h2 id="heading-lets-make-it-suck-less">Let's make it suck less</h2>
<p>Do you know what also sucks? Authentication. It sucks for users (because they create oh so many accounts), it sucks for developers (because it's so complex) and it sucks for security (because passwords are vulnerable).</p>
<p>So at least, let's try to make authentication suck less and use <a target="_blank" href="https://passwordless.id">Passwordless.ID</a>. Think of it as a free universal account, a free public service, that makes your developer's life easy, is comfortable for the users and is more secure.</p>
<p>In the meantime, we'll start porting the "tiered" app to a "bundled" app, making it even better, swifter and more lightweight. Thanks for reading and stay tuned!</p>
]]></content:encoded></item><item><title><![CDATA[Replacing avatar portraits]]></title><description><![CDATA[Currently, the registration procedure at Passwordless.ID had three steps:

Register your device with biometrics/PIN

Select your portrait.

Fill in the profile information.

View your profile and continue.


The second step, the portrait selection, w...]]></description><link>https://blog.passwordless.id/replacing-avatar-portraits</link><guid isPermaLink="true">https://blog.passwordless.id/replacing-avatar-portraits</guid><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Mon, 10 Jul 2023 21:27:57 GMT</pubDate><content:encoded><![CDATA[<p>Currently, the registration procedure at <a target="_blank" href="https://passwordless.id">Passwordless.ID</a> had three steps:</p>
<ol>
<li><p>Register your device with biometrics/PIN</p>
</li>
<li><p>Select your portrait.</p>
</li>
<li><p>Fill in the profile information.</p>
</li>
<li><p>View your profile and continue.</p>
</li>
</ol>
<p>The second step, the portrait selection, was kind of peculiar: it was a selection of icon-like characters. For the fans, here is an original screenshot.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688381708039/74f97eb8-05c3-4022-865b-7a06d2d61965.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-lets-remove-that">Let's remove that!</h2>
<p>We wanted to improve the user experience by skipping this portrait selection altogether. Why? To ease registration even more: one step less and one less choice for the user during registration.</p>
<p>Instead, registration now provides a neutral-looking, randomly generated avatar picture. Simply a gradient with the username's first letter.</p>
<p>Here is what the generated portraits look like in their round versions:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688935072278/518b5e48-4691-4629-99db-026305cb9486.png" alt class="image--center mx-auto" /></p>
<p>...and in their square version:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688934969268/9ddcf00e-7109-4ea0-b174-8764797e1972.png" alt class="image--center mx-auto" /></p>
<p>Of course, every avatar is unique, with gradient direction and colors depending on the whole username, not just the first letter.</p>
]]></content:encoded></item><item><title><![CDATA[FastAPI - Authentication demo with Passwordless.ID]]></title><description><![CDATA[This minimal example shows an integration between FastAPI and Passwordless.ID.

Source code: https://github.com/passwordless-id/fast-api-demo
Running it
To run it: uvicorn main:app
And open http://localhost:8000/docs
For authentication, you can choos...]]></description><link>https://blog.passwordless.id/fastapi-authentication-demo-with-passwordlessid</link><guid isPermaLink="true">https://blog.passwordless.id/fastapi-authentication-demo-with-passwordlessid</guid><category><![CDATA[FastAPI]]></category><category><![CDATA[Passwordless]]></category><category><![CDATA[OpenID Connect]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Mon, 26 Jun 2023 10:30:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1687438503599/40fadb15-be01-460e-b648-6eb67e9351d8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>This minimal example shows an integration between FastAPI and Passwordless.ID.</p>
</blockquote>
<p>Source code: <a target="_blank" href="https://github.com/passwordless-id/fast-api-demo">https://github.com/passwordless-id/fast-api-demo</a></p>
<h2 id="heading-running-it">Running it</h2>
<p>To run it: <code>uvicorn main:app</code></p>
<p>And open http://localhost:8000/docs</p>
<p>For authentication, you can choose either the implicit flow or the authorization code. For both, the <code>client_id</code> should be your domain ("http://localhost:8000") in our case.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687774541650/86aa2126-ba54-4896-b6ab-2c74ae24a25c.png" alt class="image--center mx-auto" /></p>
<p>For the authorization code flow, ignore the <code>client_secret</code> and leave it empty. (That sounds fishy right? A section related to security concerns is at the end of this page)</p>
<p>Once you are authenticated and authorized access, you can return the user information server side.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687774550270/8f47174b-e2b6-4315-97f7-73fe4d1c09cd.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-the-source-code">The source code</h2>
<p>The <em>whole app</em> is just this handful of lines.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Annotated

<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> Depends, FastAPI
<span class="hljs-keyword">from</span> fastapi.security <span class="hljs-keyword">import</span> OpenIdConnect
<span class="hljs-keyword">import</span> requests

app = FastAPI()

openid = OpenIdConnect(openIdConnectUrl=<span class="hljs-string">"https://api.passwordless.id/.well-known/openid-configuration"</span>)

<span class="hljs-meta">@app.get("/userinfo")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">userinfo</span>(<span class="hljs-params">authHeader: Annotated[str, Depends(<span class="hljs-params">openid</span>)]</span>):</span>
    res = requests.get(<span class="hljs-string">'https://api.passwordless.id/openid/userinfo'</span>, headers = {<span class="hljs-string">"Authorization"</span>:authHeader})
    <span class="hljs-keyword">return</span> res.json()
</code></pre>
<p>The <code>authHeader</code> contains a short-lived <em>access token</em> that will expire after a short while. It should be used during the sign-in process to fetch the user information (like username or email) and establish a normal user session. This can be done either by setting a session ID cookie and keeping the user information in memory, using a database, using a self-signed JWT or whatever. How exactly this is done is left to your own preference. Lastly, maintaining your own user session avoids uselessly repeating the request and makes the experience more snappy for the user.</p>
<h2 id="heading-security-aspects">Security aspects</h2>
<p>Let's understand what is going on in this "authorization code" flow.</p>
<ol>
<li><p>The OpenAPI docs page redirects to an URL like <code>https://api.passwordless.id/openid/authorize?...</code></p>
</li>
<li><p>Once the user authenticates and grants permission to see the profile, the user's browser is redirected back to the OpenAPI docs with a <code>code</code></p>
</li>
<li><p>Server-side, python exchanges this <code>code</code> for an access <code>token</code></p>
</li>
<li><p>With the <code>token</code>, you can send one more request to obtain the user profile</p>
</li>
</ol>
<p>In the third step, where the <em>code</em> is exchanged for the access <em>token</em>, the server usually sends a <code>client_secret</code> too, to prove it's really him. This is to protect against attacks where your <em>code</em> was leaked, to prevent the attacker to get the <em>access token</em>.</p>
<p>For Passwordless.ID, this is mitigated by only allowing redirections back to the <code>client_id</code> requested. That already avoids the code being send somewhere else by an attacker tempering with <code>redirect_uri</code>.</p>
<p>Bottom line is that as long as your front-end is not compromised, the <code>code</code> should not get leaked. It is also a "nonce", which means that once exchanged for a <em>token</em>, it is "consumed" and cannot be used again. Lastly, the <em>token</em> too is rather short lived.</p>
<p>Security-wise, the <em>authorization code</em> flow without secret is still better than the <em>implicit flow</em>. While the first exposes an exchange <em>code</em> in the browser'S URL, the latter exposes the token and user profile directly, which would make stealing it much more convinient if your front-end is compromised.</p>
<p>For even better security, we recommend using the <em>authorization code</em> with PKCE. This is similar to dynamically created secrets. Using and verifying a nonce would also be an option since it is supported by Passwordless.ID. Sadly, neither is widely supported by FastAPI nor OpenAPI.</p>
]]></content:encoded></item><item><title><![CDATA[Spring Boot - Authentication demo with Passwordless.ID]]></title><description><![CDATA[This minimalistic repository shows how to use Passwordless.ID to authenticate users.

Demo source code: https://github.com/passwordless-id/spring-boot-demo

Dependencies
Spring Boot already has everything needed built-in for OpenID authentication. Th...]]></description><link>https://blog.passwordless.id/spring-boot-authentication-demo-with-passwordlessid</link><guid isPermaLink="true">https://blog.passwordless.id/spring-boot-authentication-demo-with-passwordlessid</guid><category><![CDATA[Passwordless]]></category><category><![CDATA[OpenID Connect]]></category><category><![CDATA[Spring]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Thu, 22 Jun 2023 15:11:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1687438144004/5c12ef54-f8e1-4752-84fd-da21efdc694a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This minimalistic repository shows how to use <a target="_blank" href="https://passwordless.id">Passwordless.ID</a> to authenticate users.</p>
<blockquote>
<p>Demo source code: <a target="_blank" href="https://github.com/passwordless-id/spring-boot-demo">https://github.com/passwordless-id/spring-boot-demo</a></p>
</blockquote>
<h2 id="heading-dependencies">Dependencies</h2>
<p>Spring Boot already has everything needed built-in for OpenID authentication. Thanks to that, adding a single dependency is enough.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-oauth2-client<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
</code></pre>
<p>Adding this dependency will also add <code>spring-security</code> which will "protect" the whole web application by requiring the user to be authenticated to access anything.</p>
<h2 id="heading-openid-configuration">OpenID configuration</h2>
<p>First, in <code>resources/application.properties</code> (or ".yaml"), <em>Passwordless.ID</em> has to be declared as identity provider.</p>
<pre><code class="lang-plaintext">spring.security.oauth2.client.provider.passwordless.issuer-uri = https://api.passwordless.id
</code></pre>
<p>Then, also how to configure the authentication requests.</p>
<pre><code class="lang-plaintext">spring.security.oauth2.client.registration.passwordless.client-id = http://localhost:8080
spring.security.oauth2.client.registration.passwordless.scope = openid avatar email
</code></pre>
<p>The <code>client-id</code> <strong>must be</strong> the domain where the web application runs. As a security measure from Passwordless.ID, redirects to URLs outside your <em>client-id</em> domain will be denied. On the other hand, all redirect URLs within this domain will be allowed, without the need to register them beforehand. Also, <code>localhost</code> constitutes an exception: it is always allowed and does not require <em>https</em>.</p>
<p>The <code>scope</code> represents what you want to read from the user's profile and must be granted by the user. The scope <code>avatar</code> is a convinience scope specific to Passwordless.ID which encompasses the claims <code>nickname</code>, <code>picture</code> and <code>preferred_username</code>. It is more privacy oriented than the usual <code>profile</code> which contains the real name of the user and additional personal information.</p>
<h2 id="heading-getting-the-user">Getting the user</h2>
<p>Spring will do all the heavy lifting, and inject the OpenID Connect <code>user</code> obtained from Passwordless.ID. It works out-of-the-box, without the need to add any code.</p>
<p>In our example, only a single controller is present the user information: <code>MyController.java</code>.</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.security.core.annotation.AuthenticationPrincipal;
<span class="hljs-keyword">import</span> org.springframework.security.oauth2.core.oidc.OidcUserInfo;
<span class="hljs-keyword">import</span> org.springframework.security.oauth2.core.oidc.user.OidcUser;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;

<span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyController</span> </span>{

    <span class="hljs-meta">@GetMapping("/")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> OidcUserInfo <span class="hljs-title">getUserInfo</span><span class="hljs-params">(<span class="hljs-meta">@AuthenticationPrincipal</span> OidcUser user)</span> </span>{
        <span class="hljs-keyword">return</span> user.getUserInfo();
    }
}
</code></pre>
<h2 id="heading-try-it-out">Try it out</h2>
<p>Run the program, then open http://localhost:8080 . It should directly redirect to Passwordless.ID in order to authenticate you. Once done, it will return to the original endpoint. It should display something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687419401002/d6ed861e-a669-42aa-8258-2373f37f7b12.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-adjusting-the-security-policies">Adjusting the security policies</h2>
<p>By default, <em>all</em> endpoints are secured by Spring Security and require authentication beforehand.</p>
<p>Of course, it's possible to fine-tune which URLs require authentication, and which do not, as well as many other things. Explaining how Spring Security works and how it to configure every possible thing is beyond the scope of this minimalistic tutorial though. For a good starting point, I recommend checking out this tutorial about <a target="_blank" href="https://www.baeldung.com/spring-security-openid-connect">Spring Security and OpenID</a>.</p>
]]></content:encoded></item><item><title><![CDATA[OAuth2: Why should we validate the `redirect_uri` when exchanging the authorization code for an access token?]]></title><description><![CDATA[Beware, technical article ahead!

OAuth 2 and OpenID are complex protocols. It's full of tiny details that are there for the sake of security and it's not always clear why some checks are necessary. This article is about such a detail.
Prelude
Before...]]></description><link>https://blog.passwordless.id/oauth2-why-should-we-validate-the-redirecturi-when-exchanging-the-authorization-code-for-an-access-token</link><guid isPermaLink="true">https://blog.passwordless.id/oauth2-why-should-we-validate-the-redirecturi-when-exchanging-the-authorization-code-for-an-access-token</guid><category><![CDATA[OAuth2]]></category><category><![CDATA[oauth]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Wed, 31 May 2023 07:29:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/FnA5pAzqhMM/upload/69c46c60a90e075b48534c2920fb3620.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Beware, technical article ahead!</p>
</blockquote>
<p>OAuth 2 and OpenID are complex protocols. It's full of tiny details that are there for the sake of security and it's not always clear why some checks are necessary. This article is about such a detail.</p>
<h2 id="heading-prelude">Prelude</h2>
<p>Before stating what is missing, let us briefly look at how the OAuth2 "exchange" takes place.</p>
<ol>
<li>First is the authorization request which redirects the user to the provider:</li>
</ol>
<pre><code class="lang-http"><span class="hljs-attribute">GET /authorize
    ?response_type=code
    &amp;client_id=...
    &amp;redirect_uri=https%3A%2F%2Fexample.org%2Fcallback
    &amp;...</span>
</code></pre>
<ol>
<li>Once the user grants the scope, the callback is invoked with the <em>authorization</em> <em>code</em>. The server provided in the <code>redirect_uri</code> receives this code as follows.</li>
</ol>
<pre><code class="lang-http"><span class="hljs-attribute">https://example.org/callback
?code=Qcb0Orv1zh30vL1MPRsbm&amp;...</span>
</code></pre>
<ol>
<li>Now, at <em>example.org</em>'s server side, the <em>authorization code</em> (which is basically a nonce) is exchanged for the "real" <em>access token</em>. The URL to exchange the authorization code for a token looks like this:</li>
</ol>
<pre><code class="lang-http"><span class="hljs-attribute">POST /token
?grant_type=authorization_code
&amp;code=Qcb0Orv1zh30vL1MPRsbm
&amp;redirect_uri=https%3A%2F%2Fexample.org%2Fcallback</span>
</code></pre>
<p>Some parameters were omitted for brevity's sake, but it's sufficient for the upcoming explanation.</p>
<h2 id="heading-why-is-the-redirecturi-sent-again">Why is the `redirect_uri` sent again?</h2>
<p>Sending the <code>redirect_uri</code> in the first step is obvious, it's to know where the authorization code should be sent to via redirect.</p>
<p>However, when exchanging the authorization code for a token, why send it again?</p>
<p>Let's assume an attacker manages to manipulate the <code>redirect_uri</code> of a victim. Let's also assume this manipulated redirect is accepted by the provider for whatever reason. The attack pattern may look like this:</p>
<ol>
<li><p>The attacker achieves to manipulate the URI in the first step as follows <code>/authorize?...&amp;redirect_uri=https://attacker.xyz/callback</code></p>
</li>
<li><p>The attacker receives the authorization code <code>https://attacker.xyz/callback?code=...</code></p>
</li>
<li><p>The attacker can now invoke <code>/token?code=...&amp;redirect_uri=https://attacker.xyz/callback</code> or <code>/token?code=...&amp;redirect_uri=https://example.org/callback</code> as it please.</p>
</li>
</ol>
<p>It makes no difference since the attacker is in full control of what is sent as <code>redirect_uri</code> ...right?</p>
<p>Anyway, it's probably further protected by a client secret or PKCE. Is there a way around it?!</p>
<h2 id="heading-here-comes-the-trick">Here comes the trick!</h2>
<p>Let's assume <em>Alice</em> is the attacker and <em>Vincent</em> the victim.</p>
<p>Both <em>Alice</em> and <em>Vincent</em> have an account at <em>example.org</em>, which can for example use google photos to make collages of your family pictures.</p>
<p>Somehow Alice manages to manipulate the redirect_uri of Vincent, redirecting him to <code>https://attacker.xyz/callback?code=ABC...</code></p>
<p>This attacker's website will probably also discreetly redirect back to example.org after grabbing the code. Vincent won't see example.org working properly, probably saying something went wrong, without suspecting anything bad.</p>
<p>On the other hand, Alice could go to example.org and use Vincent's code to complete the OAuth2 flow for herself, simply by pasting <code>https://example.org/callback?code=ABC...</code></p>
<p>In other words, Alice will be able to impersonate Bob and make photo collages of Vincent's family.</p>
<p>This is of course just an example to illustrate the issue, but you certainly see the extent of this kind of exploit, which lets the attacker impersonate someone else.</p>
<p>Verifying the <code>redirect_uri</code> is there to prevent such "code stealing". It ensures such a triangle with an attacker in the middle did not take place by verifying that the code was indeed sent to the place the client app expected. Nothing more, nothing less.</p>
<h2 id="heading-how-likely-is-it-to-have-a-valid-attacker-redirecturi">How likely is it to have a valid attacker <code>redirect_uri</code>?</h2>
<p>This cannot happen if a single <code>redirect_uri</code> is defined. It happens in scenarios where multiple callback URIs are defined (or if the server check is missing!) or where some loose matching takes place. Imagine for example a service which lets developers host their own apps behind a subdomain, providing authentication as a convenience. This could lead to a legitimate app goodapp.example.org being manipulated to leak the code to badapp.example.org, which might have a legitimate <code>redirect_uri</code> for example.org too. This could also be a larger organization, where some isolated app was hacked, and so on.</p>
]]></content:encoded></item><item><title><![CDATA[Passwordless.ID - Sessions]]></title><description><![CDATA[I'll start this blog entry with a question I received in the Discord chat last week.

I would like to automatically log out the current user if there is no activity for x time. Haven't thought of the implementation, but is there an API for that?

Bef...]]></description><link>https://blog.passwordless.id/passwordlessid-sessions</link><guid isPermaLink="true">https://blog.passwordless.id/passwordlessid-sessions</guid><category><![CDATA[Passwordless]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Sat, 11 Mar 2023 10:24:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Cv1IZqKJQzU/upload/09807d43454e97f735ef00ccfa9cad05.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I'll start this blog entry with a question I received in the <a target="_blank" href="https://discord.gg/v73cHHnqzZ">Discord chat</a> last week.</p>
<blockquote>
<p>I would like to automatically log out the current user if there is no activity for x time. Haven't thought of the implementation, but is there an API for that?</p>
</blockquote>
<p>Before answering that, let's start with the basics.</p>
<h2 id="heading-what-is-a-session">What is a session?</h2>
<p>HTTP is at its core a stateless protocol. In order to "remember" things (like products in a shopping cart), associating the HTTP requests to a certain user (signed in or anonymous) is required. This is also called a session and is typically done in either of two ways:</p>
<ul>
<li><p>A Cookie storing some ID</p>
<ul>
<li><p>Automatically sent by the browser on each request</p>
</li>
<li><p>For security reasons, it is <em>highly recommended</em> to enforce <code>Secure</code> (ensures HTTPS) and <code>HttpOnly</code> (inaccessible by javascript) cookies.</p>
</li>
<li><p>Session tracking can be performed before the user even signs in, or even be unrelated</p>
</li>
</ul>
</li>
<li><p>An Authorization header with some token</p>
<ul>
<li><p>The token can be anything: a JWT from a thrid-party, an API key...</p>
</li>
<li><p>Tokens must be sent explicitly in the <code>Authorization</code> header for each HTTP request</p>
</li>
<li><p>Tokens add some logic client side to obtain them, refresh them and potentially cache them</p>
</li>
</ul>
</li>
</ul>
<p>On the server side, the cookie or token is read, making it possible to associate the HTTP request to the adequate "session".</p>
<p>Cookies are the classic way to handle sessions server side, they are simple, secure and practical. On the other hand, Json Web Tokens (JWT) are often used to obtain a signed token from a third party to act as proof of identity or for distributed APIs.</p>
<h2 id="heading-whose-session-is-it">Whose session is it?</h2>
<p>When using Passwordless.ID, there are actually two sessions involved.</p>
<ul>
<li><p>The session between <strong>example.xyz</strong> and the user</p>
</li>
<li><p>The session between <strong>passwordless.id</strong> and the user</p>
</li>
</ul>
<p>Please note also that a session for <strong>example.xyz</strong> is not strictly required. It could simply directly forward the <code>id_token</code> from <strong>passwordless.id</strong> directly to the server/APIs with <code>Authorization: Bearer {{id_token}}</code> and not use sessions at all. Or, it could send it once at the beginning to establish a cookie based session.</p>
<p>The session of <em>Passwordless.ID</em> is independent, a bit like how you are currently signed in with Google/Microsoft/Apple. If you are currently signed in with <em>Passwordless.ID</em>, every website you visit using <em>Passwordless.ID</em> will see you as signed in, and when you sign out or switch the account, it will reflect on all other websites too. That is also why you cannot forcibly sign out the user from <em>Passwordless.ID</em>. This would affect everyone.</p>
<p>Typically, when <strong>example.xyz</strong> requires authentication, it will perform a call to <code>passwordless.id()</code> using the <a target="_blank" href="https://www.npmjs.com/package/@passwordless-id/connect">@passwordless-id/connect</a> packacge. If the <strong>passwordless.id</strong> session is still alive, a brand new <code>id_token/token</code> will be returned without user interaction, also called silent authentication. Otherwise, if the <strong>passwordless.id</strong> session expired or because the user deliberately signed out, the sign in dialog will appear before the <code>id_token/token</code> is returned.</p>
<p>Lastly, the user information of <em>Passwordless.ID</em> <strong>might</strong> be cached by other websites in their own sessions. This results in the signed in effect appear to be "sticky" even if the user has signed out from Passwordless.ID separately.</p>
<h2 id="heading-so-what-about-inactivity-expiration">So what about inactivity expiration?</h2>
<p>Activity on <em>your</em> website is for <em>you</em> to track. Whether you use a timer in the browser or check with your server if the session still exists is up to you. How you keep track of activity and expiration delays are all under your control.</p>
<p>But as stated before, even if the session at <code>example.xyz</code> expires, the session at <strong>passwordless.id</strong> might still be active, silently re-authenticating automatically.</p>
<h2 id="heading-but-i-want-explicit-re-authentication">But I want explicit re-authentication!</h2>
<blockquote>
<p>Currently, this is unavailable. However, future support for this is planned as explained below.</p>
</blockquote>
<p>While silent re-authentication might be really comfortable in some cases, in other cases you may <em>want</em> explicit re-authentication. This is especially true in security sensitive contexts like banking or finance-related apps. After some inactivity delay, explicitly re-verify the user may be required.</p>
<p>Moreover, this explicit user verification might also be desired <em>during</em> a session, for example when accessing a restricted area or before triggering some sensitive operation. For example, it is common to verify the user before allowing a payment.</p>
<p>To do that, an additional parameter <code>prompt</code> will be offered in an <em>upcoming version</em>.</p>
<pre><code class="lang-javascript">/openid/authorize?...&amp;prompt=login
</code></pre>
<p>This upcoming parameter will explicitly re-trigger authentication and verify the user using biometrics or local authentication. This can be used as proof for sensitive operations or to re-sign-in users <em>explicitly</em> after an expired session.</p>
<p>This can be invoked through the <a target="_blank" href="https://www.npmjs.com/package/@passwordless-id/connect">@passwordless-id/connect</a> lib as follows.</p>
<pre><code class="lang-js">passwordless.id({
    <span class="hljs-attr">prompt</span>: <span class="hljs-string">'login'</span>
})
</code></pre>
<p>On the opposite, <code>prompt: 'none'</code> (the default) would perform silent authentication if possible.</p>
<p>By default, the retrieved <code>token/id_token</code> is cached in <code>sessionStorage</code>. When you want to keep track of the session on your own, it is also recommended not to cache the token, to avoid accidentally re-using the cached token.</p>
<pre><code class="lang-js">passwordless.id({
    <span class="hljs-attr">cache</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">prompt</span>: <span class="hljs-string">'login'</span>
})
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Weekly news]]></title><description><![CDATA[I try to post something each week, but it isn't always easy. This week was calm. Currently, Passwordless.ID is at the MVP stage (Minimal Viable Product), or the rather the beta version of it. It still has some rough edges. However, it works.
That is ...]]></description><link>https://blog.passwordless.id/weekly-news</link><guid isPermaLink="true">https://blog.passwordless.id/weekly-news</guid><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Sat, 04 Mar 2023 14:32:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/xG8IQMqMITM/upload/9edab61e3f7740dcb2af39b5545f23a8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I try to post something each week, but it isn't always easy. This week was calm. Currently, Passwordless.ID is at the MVP stage (Minimal Viable Product), or the rather the beta version of it. It still has some rough edges. However, it works.</p>
<p>That is why it is IMHO time for an early push in adoption and community building rather than developing the next feature. After all, isn't it pointless to develop something if nobody uses it? Building a community around it is a challenge itself.</p>
<h2 id="heading-sponsorship-available">Sponsorship available</h2>
<p>At last! Dunno why but GitHub sure took its time to approve the sponsorship. Now that it's there, <a target="_blank" href="https://github.com/sponsors/passwordless-id">check it out</a>! Be the first sponsor! Yay! ...even if it's a single dollar and not enough to buy a coffee, it doesn't matter, it's the gesture that counts.</p>
<p>I also wonder if Github Sponsors is the right choice or if I should try to place an alternative funding platform like Patreon or OpenCollectives. What do you think?</p>
<h2 id="heading-discord-channel-added">Discord channel added</h2>
<p><a target="_blank" href="https://discord.gg/v73cHHnqzZ">Come chat</a> if you like! ...it's brand new and a ghost town right now. The community is non-existent at this point, so <em>be</em> a community pioneer and leave a message. ;)</p>
<h2 id="heading-reserve-your-username-now">Reserve your username now!</h2>
<p>...pick a cool username before you're stuck with something like "dsiufgziuzgi45678" in the far future.</p>
<h2 id="heading-whats-next">What's next?</h2>
<p>I'm divided between reaching out for early sponsors or further developing it to make it better. It is still lacking a bit in some areas. I think I will do something in-between and reach out to two organizations I have in mind, then continue developing it before broadening the outreach.</p>
<p>Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[WebAuthn vs Passkeys]]></title><description><![CDATA[In case you heard about Passkeys and WebAuthn, you might wonder what are the differences, or their relationship. This article tries to clarify what both are and shares some personal opinions about the implications of Passkeys.
What is WebAuthn?
WebAu...]]></description><link>https://blog.passwordless.id/webauthn-vs-passkeys</link><guid isPermaLink="true">https://blog.passwordless.id/webauthn-vs-passkeys</guid><category><![CDATA[#webauthn]]></category><category><![CDATA[passkeys]]></category><category><![CDATA[authentication]]></category><category><![CDATA[Passwordless]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Sun, 26 Feb 2023 17:11:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677430131829/db9c50b3-8758-485a-ad40-f27f65e936bb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In case you heard about Passkeys and WebAuthn, you might wonder what are the differences, or their relationship. This article tries to clarify what both are and shares some <em>personal opinions</em> about the implications of Passkeys.</p>
<h2 id="heading-what-is-webauthn">What is WebAuthn?</h2>
<p>WebAuthn is a <a target="_blank" href="https://www.w3.org/TR/WebAuthn-3/">specification</a>. It is an "RFC" issued by W3C, standing for "Web Authentication" to specify a set of interfaces for browsers to implement.</p>
<h3 id="heading-how-does-it-work">How does it work?</h3>
<p>Fundamentally, the protocol's concept is rather straightforward.</p>
<p>During registration, the device (called "authenticator") generates a cryptographic key pair. The public key is sent to the server and the private key is safely stored locally.</p>
<p>During authentication, the server then asks the client to sign a message with a nonce (called "challenge") with its private key pair. This signed message with the nonce can then be verified by the server using the public key obtained through registration.</p>
<p>Moreover, the private key was never exposed. The signing of the message is done directly by the "authenticator", the device, and protected by some form of <em>local</em> user verification (like fingerprint, face, local PIN, swipe pattern, etc).</p>
<p>This had really strong security properties because the private key was hardware bound, and needed user's local verification to access it.</p>
<p>Why did I use the past tense? Because of ...Passkeys!</p>
<h2 id="heading-what-are-passkeys">What are Passkeys?</h2>
<p>Passkeys is basically the platform's implementations of the WebAuthn. Until now, the protocol had a shortcoming: if the user lost their device, they lost the private key and were locked out of their accounts. Bam!</p>
<p>The websites and users would have to either let them register multiple devices, with an associated key pair each, or rely on a (possibly less secure) account recovery mechanism.</p>
<p>To circumvent that, Apple, Google and Microsoft decided to go forward with "Passkeys". This is basically nothing else than WebAuthn <em>but</em> they sync your private keys into the cloud. Your private keys are not tied to the device anymore, they are "backed up" in the cloud, as they say.</p>
<p>It's like handing over a copy of your keys to Apple/Google/Microsoft saying "Here are my keys guys, keep it safe!". This is both a boon and a curse. On one hand, the users don't have to worry about losing their keys. On the other hand, it has of course strong implications on security, privacy and vendor lock-in.</p>
<p>Security-wise, the attack surface becomes bigger: on the device level (since it goes in an out of the secure hardware key store), on the network level by intercepting the syncing mechanism, on the vendor level in case of data breach and on the recovery level with someone trying to impersonate you.</p>
<p>Privacy-wise, they have your keys. Whether it's for their purpose, because of the NSA or whatever agency, they could access all your private accounts. That's a question of thrust here that they don't do.</p>
<p>Regarding vendor lock-in, if they have all your keys and you want to switch the platform ...well, you got a problem. You would probably have to re-register or launch recovery procedures on most accounts. It's quite a pain, and more sneaky since it only becomes obvious once it's too late. If keys were not synced, websites would put more effort into supporting multiple keys per account, thus being cross-platform by default. But with Passkeys they'll likely prefer to let the platform handle it.</p>
<h2 id="heading-passkeys-in-practice">Passkeys in practice</h2>
<p>The main <a target="_blank" href="https://github.com/w3c/WebAuthn/issues/1739">issue</a> is also that you cannot opt in or out. The WebAuthn protocol lets the platform dictate. It was also raised in this issue that this is problematic: reduced security guarantees, regulations that cannot be applied, no way to block insecure device models, etc... However, the spec is driven by the big player's implementations rather than the other way around.</p>
<p>The other issue is that, in reality, it turns out to be more complex for websites to deal with ...at least, if you want to do it properly. Before Passkeys, you just had to let the users register multiple devices or have sound recovery mechanisms. Now, websites have to deal with devices that are maybe synced, or maybe not, or maybe not yet. Perhaps the user used a hardware key, which is not synced, or disabled it somehow if the platform permits it, or simply hasn't synced the key yet. In other words, there are more cases to take into account while still needing the same safety measures to avoid that a device loss leads to account "lock-out".</p>
<p>Lastly, these security, privacy and vendor lock-in effects are not recognized by average users. They just want to sign in. It is our duty as developers to deal with these issues responsibly.</p>
<h2 id="heading-webauthn-in-practice">WebAuthn in practice</h2>
<p>In practice, it's a mess. The specification is overly complicated, long, unclear and there is a clear gap between the specs and implementations.</p>
<p>For example, let me cite the <em>second</em> sentence of the abstract.</p>
<blockquote>
<p>Conceptually, one or more public key credentials, each scoped to a given WebAuthn Relying Party, are created by and <em>bound</em> to authenticators as requested by the web application.</p>
</blockquote>
<p>Well, it's simply not true anymore since mid-2021 and noted in an <a target="_blank" href="https://github.com/w3c/WebAuthn/issues/1665">issue</a> but the specs remains unchained until now.</p>
<p>So, the specs differ from implementations and every platform and browser will deliver you another experience... which is IMHO still seriously lacking. Moreover, "Open" Browsers like Firefox are struggling and lagging behind with partial implementations due to the complexity of the specs, Linux as well...</p>
<p>So, although it's great on paper, the practical experience is a hit-or-miss thing. As it stands now, I strongly recommend to offer an alternative sign in experience if WebAuthn isn't properly supported or if "it doesn't work properly" on the user's device.</p>
<p>Moreover, it's not for the faint of heart. As a developer, you will have to deal with ArrayBuffers using a CBOR encoding and lots of low-level things. The whole process is not simple either, for example, <a target="_blank" href="https://w3c.github.io/WebAuthn/#sctn-registering-a-new-credential">registration</a> is described as a 26 step procedure and <a target="_blank" href="https://w3c.github.io/WebAuthn/#sctn-verifying-assertion">authentication</a> requires 23 steps to verify. Have fun! (sarcastic tone)</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In my humble opinion, WebAuthn is an awesome concept with terrible execution. The promise is to get vastly improved security and convenience in the same package. However, what you get is a spec that a normal person cannot read, incredible complexity for developers, and browser implementations that are sometimes not working as expected or with poor UX. So here goes your vastly improved security with the touch of a finger ...down the drain. It's just a lot of painful work to get it right properly.</p>
<p>Moreover, it feels like the "big players" move to Passkeys was mostly a hype attempt ...and to pursue their own agenda of user retention rather than being truly helpful to the developer community. They wanted a new start, to remove the hurdle of multiple keys per account and encourage adoption that way. However, it does this at the cost of security, privacy and lock-in, so while useful it is a steep price. Moreover, "rebranding" the struggling WebAuthn specs as Passkeys rather adds to the confusion while not resolving the core problems, namely the complexity of the protocol itself.</p>
<p>So what's my take on it? If you have sufficient resources and time to pour into this, yes, you should support WebAuthn/Passkeys. It <em>is</em> better security-wise and depending how it's implemented, it might be more convenient too. However, if you do not, use an existing authentication provider. Using OpenID/OAuth2 is really <em>much simpler</em> than WebAuthn. You can also combine both by using <a target="_blank" href="http://Passwordless.ID">Passwordless.ID</a> for example, it's a public free identity provider offering passwordless authentication using WebAuthn. <a target="_blank" href="https://passwordless-id.github.io/demo/">Try it</a> if you want.</p>
<p>Thanks for reading and feel free to share your opinion!</p>
]]></content:encoded></item><item><title><![CDATA[Passwordless.ID - Screenshots 🖥️]]></title><description><![CDATA[Passwordless.ID is a free public identity provider allowing users to sign in/up in web apps using their fingerprint, face recognition or local authentication mechanisms like swipe pattern or PIN code. The results are no more passwords, a much smoothe...]]></description><link>https://blog.passwordless.id/passwordlessid-screenshots</link><guid isPermaLink="true">https://blog.passwordless.id/passwordlessid-screenshots</guid><category><![CDATA[authentication]]></category><category><![CDATA[Screenshot]]></category><category><![CDATA[#webauthn]]></category><category><![CDATA[passkeys]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Sun, 19 Feb 2023 21:18:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676837315275/ed6af910-4adf-401a-843f-895c965b5fcc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><a target="_blank" href="https://passwordless.id/">Passwordless.ID</a> is a free public identity provider allowing users to sign in/up in web apps using their fingerprint, face recognition or local authentication mechanisms like swipe pattern or PIN code. The results are no more passwords, a much smoother user experience and vastly improved security. It provides two-factor authentication using a single touch or a smile in the camera. Awesome, right? <a target="_blank" href="https://passwordless-id.github.io/demo/">Try it now</a>, it's a public service free forever, no account necessary!</p>
</blockquote>
<p>Since a picture is worth a thousand words, here it comes... and actually, a bunch of them.</p>
<h2 id="heading-authentication-on-various-platforms">Authentication on various platforms</h2>
<p>I used my own phone/laptop with a German locale, sorry guys. 😅</p>
<p>The cornerstone of this authentication service is to delegate user verification to the local platform. You may use your device's fingerprint sensor, some swipe pattern, windows hello with face recognition or even plain old passwords ...whatever is configured on your device locally.</p>
<p><img src="https://passwordless.id/screenshots/authenticate-android-fingerprint.jpg" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/authenticate-windows-fingerprint.jpg" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/authenticate-windows-local-pin.png" alt class="image--center mx-auto" /></p>
<p>By the way, if anybody could provide me a screenshot from a Mac or IPhone, that would be great!</p>
<h2 id="heading-creating-a-new-user">Creating a new user</h2>
<p>Creating a new user only requires a username. An e-mail address (although useful) is entirely optional. Why? Because there is no need for "I forgot my password"!</p>
<p><img src="https://passwordless.id/screenshots/create-user-taken.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/create-user-avail.png" alt class="image--center mx-auto" /></p>
<p>Upon clicking "Create Account" you will be prompted for local authentication. Once done your account is created. That's it. What follows is simply defining your profile ...if you want to.</p>
<p><img src="https://passwordless.id/screenshots/create-avatar-picker.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/edit-profile-small.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/edit-profile-large.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-signing-in">Signing in</h2>
<p><img src="https://passwordless.id/screenshots/pick-user.png" alt class="image--center mx-auto" /></p>
<p>Once you pick your avatar, you must prove it's you by using local authentication. Don't worry, none of the information like fingerprint or pin codes is ever sent to the server. It is used locally to <em>sign</em> a message using asymmetric cryptography and prove you are you.</p>
<p><img src="https://passwordless.id/screenshots/view-profile-mixed-recovery.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-oauth2-openid-flow">OAuth2 / OpenID flow</h2>
<p>That is the whole purpose of this service. So that any website on the internet can easily ask "Who are you?". Since it is public, you can use it out of the box, even without account.</p>
<p><img src="https://passwordless.id/screenshots/openid-start.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/openid-pick-user.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/openid-user.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/openid-authorize.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/openid-end.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-managing-devices">Managing devices</h2>
<p>One of the more unusual aspects of this kind of authentication is that it is more secure by default. Only your registered devices can sign in. As such, it is important to easily add a new device.</p>
<p><img src="https://passwordless.id/screenshots/add-device-before.png" alt class="image--center mx-auto" /></p>
<p>Once you scan the QR code, you will be able to register your other device directly.</p>
<p><img src="https://passwordless.id/screenshots/devices-register.png" alt class="image--center mx-auto" /></p>
<p>And after a while, you may end up with several authorized devices.</p>
<p><img src="https://passwordless.id/screenshots/devices-triple.png" alt class="image--center mx-auto" /></p>
<p>If you cannot scan the QR code or open the link in the e-mail, you can also enter the OTP code manually.</p>
<p><img src="https://passwordless.id/screenshots/devices-recovery.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/devices-otp-code.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-recovery-options">Recovery options</h2>
<p>Now, if you have registered only a single device, without email or phone, you might be in trouble. In that case, losing your device would make it impossible to connect to your account anymore. That's why the interface emphasizes user guidance. It is a new topic for the users too.</p>
<p><img src="https://passwordless.id/screenshots/view-profile-no-recovery.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/view-profile-half-recovery.png" alt class="image--center mx-auto" /></p>
<p>After a classic e-mail confirmation, it looks already much better. Alternatively, you could also have just registered another device per QR Code. Both are ways to ensure safety.</p>
<p><img src="https://passwordless.id/screenshots/email-confirm.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/email-confirmed.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/view-profile-mixed-recovery-open.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-e-mail-confirmation-and-recovery">E-Mail confirmation and recovery</h2>
<p><img src="https://passwordless.id/screenshots/email-recovery-options.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/email-recovery-sent.png" alt class="image--center mx-auto" /></p>
<p><img src="https://passwordless.id/screenshots/email-sign-in.png" alt class="image--center mx-auto" /></p>
<hr />
<p>IMHO the screenshots do not really convey the usage and the feeling of this "service" very well. However, it might give a slight idea of it. I recommend trying out the <a target="_blank" href="https://passwordless-id.github.io/demo/">demo</a> if you are curious or looking at the <a target="_blank" href="https://passwordless.id/">main site</a>.</p>
<p>Although it's usable and has the fundamental things working right, it is still a bit rough around the edges. It still has to be fleshed out regarding certain aspects and needs some polish too. But it's working. It is in a kind of an early preview.</p>
<p>That is why feedback is always welcome!</p>
]]></content:encoded></item><item><title><![CDATA[E-Mail verification now available]]></title><description><![CDATA[According to our roadmap, e-mail verification and account recovery is now available. At least, the fundamental parts.
A verification e-mail is automatically when the user sets or updates it in the profile.

Thanks to a verified e-mail, it can also be...]]></description><link>https://blog.passwordless.id/e-mail-verification-now-available</link><guid isPermaLink="true">https://blog.passwordless.id/e-mail-verification-now-available</guid><category><![CDATA[features]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Sun, 12 Feb 2023 14:13:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/LPZy4da9aRo/upload/217bf4c6cb2e44df432696e236c92616.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>According to our <a target="_blank" href="https://github.com/orgs/passwordless-id/projects/1/views/1">roadmap</a>, e-mail verification and account recovery is now available. At least, the fundamental parts.</p>
<p>A verification e-mail is automatically when the user sets or updates it in the profile.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676209790115/2cd130fc-dfc4-4efc-886f-01f78e734e11.png" alt class="image--center mx-auto" /></p>
<p>Thanks to a verified e-mail, it can also be used to recover an account or register a new device.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676209850137/8d97f83f-0d9e-41c6-9be0-09289a4ec7e4.png" alt class="image--center mx-auto" /></p>
<p>...well, the e-mail template is far from perfect. The wording is a bit amiss and the "{{ method }}" is superfluous.</p>
<p>But as with most things, there is still much to do beyond fundamentals. Actually, the whole "recovery" mail is a bit misleading. It should rather just be called "Sign in link" instead and in the welcome screen let the user decide if the device should be registered or if it's a temporary session.</p>
<p>Among other things to do...</p>
<ul>
<li><p>Change "recovery" in "Sign in e-mail"</p>
</li>
<li><p>A nicer "Sign in email sent" view</p>
</li>
<li><p>An "email (un)verified" hint in the profile</p>
</li>
<li><p>A "send confirmation email again" button</p>
</li>
<li><p>Allow usage of e-mail instead of username too</p>
</li>
<li><p>Check whether the e-mail was really properly sent</p>
</li>
<li><p>Nicer error messages</p>
</li>
</ul>
<p>Even for simple functionality, there is always many things to do. It might look like details, but that is what makes the difference between a great polished UI and an unconvincing one.</p>
<p>Thanks for reading,<br />See you next time,<br />Arnaud</p>
]]></content:encoded></item><item><title><![CDATA[Passwordless.ID - Roadmap]]></title><description><![CDATA[Passwordless.ID is a free public identity provider allowing users to sign in/up in web apps using their fingerprint, face recognition or local authentication mechanisms like swipe pattern or PIN code. The results are no more passwords, a much smoothe...]]></description><link>https://blog.passwordless.id/passwordlessid-roadmap</link><guid isPermaLink="true">https://blog.passwordless.id/passwordlessid-roadmap</guid><category><![CDATA[authentication]]></category><category><![CDATA[OpenID Connect]]></category><category><![CDATA[login]]></category><category><![CDATA[passkeys]]></category><category><![CDATA[#webauthn]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Tue, 07 Feb 2023 08:00:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/vv-oEGlN-4E/upload/bd032aea2a0efbccb000ba4ebc07a3a6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><a target="_blank" href="https://passwordless.id/">Passwordless.ID</a> is a free public identity provider allowing users to sign in/up in web apps using their fingerprint, face recognition or local authentication mechanisms like swipe pattern or PIN code. The results are no more passwords, a much smoother user experience and vastly improved security. It provides two-factor authentication using a single touch or a smile in the camera. Awesome, right? <a target="_blank" href="https://passwordless-id.github.io/demo/">Try it now</a>, it's a public service free forever, no account necessary!</p>
</blockquote>
<p>Things have been calm lately as I had to shift my focus to other tasks in my life. Nevertheless, things are going forward, although at a slower pace than I would wish to be.</p>
<p>For those who are curious about where it's headed to and what's still to be done, I invite you to check the following roadmap!</p>
<p><a target="_blank" href="https://github.com/orgs/passwordless-id/projects/1">Passwordless.ID Roadmap</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675756774919/4508b7fa-19b2-47a8-b5ab-59ac07613ead.png" alt class="image--center mx-auto" /></p>
<p>The next steps are (at last) verifying the user's e-mail. In case you haven't experienced yourself, Passwordless.ID does not require an e-mail at all. It's optional. However, it's both important information to verify if it is provided, and a potential recovery mechanism in case of device loss.</p>
<p>Since only registered devices can be used to authenticate the user, the only "safe" way to use Passwordless.ID is to register at least a second device.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675756141328/d16a5ef0-5237-491b-a75a-2c870e9f8d09.png" alt class="image--center mx-auto" /></p>
<p>However, this is currently not so obvious. ...I would even say "dangerous" right now because an average user would not be aware that losing their device would make their account inaccessible. That's also why there is a TODO on the roadmap: to make such information more obvious in the UI and encourage the user to register a second device or verify e-mail/phone information during registration itself.</p>
<p>Stay tuned!</p>
]]></content:encoded></item><item><title><![CDATA[Passwordless authentication for your website in 5 minutes!]]></title><description><![CDATA[Passwordless authentication using your phone's fingerprint sensor, or face recognition using your webcam is now possible in the browser thanks to the WebAuthn protocol. It is not only more comfortable for users but also more secure since it is two-fa...]]></description><link>https://blog.passwordless.id/passwordless-authentication-for-your-website-in-5-minutes</link><guid isPermaLink="true">https://blog.passwordless.id/passwordless-authentication-for-your-website-in-5-minutes</guid><category><![CDATA[authentication]]></category><category><![CDATA[Passwordless]]></category><category><![CDATA[OpenID Connect]]></category><category><![CDATA[#webauthn]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Fri, 13 Jan 2023 10:05:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673520209415/1b854eb6-cfb4-4028-a94c-ed243f5d5429.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673520949822/ffe4e78d-4b66-41cb-ac5c-fbba9d13572f.png" alt class="image--center mx-auto" /></p>
<p>Passwordless authentication using your phone's fingerprint sensor, or face recognition using your webcam is now possible in the browser thanks to the WebAuthn protocol. It is not only more comfortable for users but also more secure since it is two-factor authentication in a single step.</p>
<p>In this demo, we will use the free <a target="_blank" href="https://passwordless.id">Passwordless.ID</a> API. This is a <em>public identity provider</em>, and it will take care of the heavy lifting for us. It's free and you don't even need to register an account or download anything to use it.</p>
<p>The following picture describes what we are about to do.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673520332515/a8d89dc2-df1f-4a73-bdc4-70adf9f3ea4c.png" alt class="image--center mx-auto" /></p>
<p>Our demo website will simply redirect to <em>passwordless.id</em> to authenticate "passwordlessly" (no e-mail required either) and authorize reading the profile, then return to the original demo page.</p>
<p>Thanks to it, we will obtain the user's avatar for that demo and display it, that's it! What is also obtained, besides the user's profile, is a signed Json Web Token. This token can later be sent to your APIs as proof of the user's identity. More on that later.</p>
<hr />
<h1 id="heading-live-demo">Live Demo</h1>
<p><mark>Please note that the authentication will be blocked within the IFrame for security reasons, click on the upper right to open the CodeSandbox in its own tab!</mark></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codesandbox.io/embed/zealous-mountain-ej6q48?fontsize=14&amp;theme=dark">https://codesandbox.io/embed/zealous-mountain-ej6q48?fontsize=14&amp;theme=dark</a></div>
<p> </p>
<p>If somehow the CodeSandbox is not shown properly or does not work, you can also see the live demo <a target="_blank" href="https://passwordless-id.github.io/demo/">here</a> and the source code <a target="_blank" href="https://github.com/passwordless-id/demo">here</a>.</p>
<p>The Passwordless.ID API follows the OAuth2 / OpenID standards, just like google or facebook logins. It is nevertheless simpler and easier to integrate with since it is a <em>public</em> API, not requiring any kind of special authorization.</p>
<hr />
<h1 id="heading-step-by-step-tutorial">Step by step tutorial</h1>
<p>Although it is compatible with most other "Sign in with..." client libraries as a generic OpenID provider, the easiest way to integrate with it though is to use the <a target="_blank" href="https://github.com/passwordless-id/connect">@passwordless-id/connect</a> library.</p>
<p>Let us go through the code, starting with the "Sign In" button.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"sign-in"</span> <span class="hljs-attr">hidden</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-light"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"app.signIn()"</span>&gt;</span>Sign In<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Now, this is very basic, and depending on the frontend framework you use it will look different, but this is the gist of it.</p>
<p>The <code>app.signIn()</code> looks as follows.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> passwordless <span class="hljs-keyword">from</span> <span class="hljs-string">'https://unpkg.com/@passwordless-id/connect'</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">signIn</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// redirects to the authentication/authorization page and back</span>
    passwordless.auth({<span class="hljs-attr">scope</span>: <span class="hljs-string">'openid avatar email'</span>})
}

<span class="hljs-comment">// a global `app` object just for the purpose of the demo</span>
<span class="hljs-built_in">window</span>.app = {
    signIn
}
</code></pre>
<p>Once the <code>passwordless.auth()</code> method is called, the user will be redirected to the Passwordless.ID UI which will prompt it to create an account or sign in and then to authorize your app to read the <code>openid</code>, <code>avatar</code> and <code>email</code> of your profile. In case the user is already signed in and has granted access, the redirect would come back directly to the app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673556587525/7f3d2d75-6aa5-4778-b361-72bc26e520be.png" alt class="image--center mx-auto" /></p>
<p>When redirecting back, it will append the <code>id_token</code> to the URL hash value (<code>https://example.com/mypage#id_token=...</code>) . The profile can then be extracted from the URL using the following piece of code.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> passwordless.id({<span class="hljs-attr">scope</span>: <span class="hljs-string">'openid avatar email'</span>})
<span class="hljs-keyword">if</span>(user.signedIn &amp;&amp; user.scopeGranted) {
    <span class="hljs-comment">// great, user.profile contains the information</span>
    <span class="hljs-comment">// and user.id_token can be sent to APIs</span>
}
    <span class="hljs-comment">// the user is not signed in or has refused</span>
    <span class="hljs-comment">// to grant authorization</span>
}
</code></pre>
<p>This chuck of code is typically executed when loading the page. The <code>id()</code> function will first look at the current URL and try to parse the <code>id_token</code> in the hash if present. Otherwise, a request will directly be sent to the Passwordless.ID API to obtain it (if the user is signed in and has previously granted the scope of course).</p>
<p>The function <em>always</em> returns. If the user is signed in and has granted the scope, the resulting <code>user</code> would look as follows.</p>
<pre><code class="lang-javascript">{
 <span class="hljs-string">"signedIn"</span>: <span class="hljs-literal">true</span>,
 <span class="hljs-string">"scopeGranted"</span>: <span class="hljs-literal">true</span>,
 <span class="hljs-string">"id_token"</span>: <span class="hljs-string">"eyJ0eXAiOiJK..."</span>,
 <span class="hljs-string">"profile"</span>: {
  <span class="hljs-string">"nickname"</span>: <span class="hljs-string">"Johny"</span>,
  <span class="hljs-string">"picture"</span>: <span class="hljs-string">"https://ui.passwordless.id/avatars/sam.svg"</span>,
  <span class="hljs-string">"preferred_username"</span>: <span class="hljs-string">"johndoe"</span>,
  <span class="hljs-string">"..."</span>: <span class="hljs-string">"...more attributes depending on requested scope"</span>
 }
}
</code></pre>
<p>That's it! You have signed in a user and read its profile!</p>
<p><img src="https://media.giphy.com/media/lOPJZITKZYLiU3cfGl/giphy.gif" alt class="image--center mx-auto" /></p>
<p>What you might also need is a way to sign out the user. Since Passwordless.ID is a centralized authentication system, being signed in or out affects all websites using it. As such, you cannot forcibly sign out users. You can ask them nicely "do you want to sign out?" using the following line.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// redirects the user to the sign out page</span>
passwordless.logout()
</code></pre>
<p>This will redirect the user to a sign out page, and once done (or not), back to the current URL.</p>
<p>Everything is now there to sign in, sign out and retrieve the user. Congrats! For the sake of brevity, we will skip the part of displaying the profile and other UI interactions. That is up to you to freely customize according to your website's style.</p>
<hr />
<h1 id="heading-what-about-the-servers">What about the servers?</h1>
<p>The example before shows browser side interaction, with the profile information being directly available. But how could you trust that information server side? That's what the <code>id_token</code> in the response is for! It is a "<a target="_blank" href="https://jwt.io">Json Web Token</a>" (JWT), a signed proof of the user's identity.</p>
<p>The <code>id_token</code> is signed usings Passwordless.ID's secret private key and can be verified using its <a target="_blank" href="https://api.passwordless.id/openid/publickey">public key</a>.</p>
<p>There are two ways to validate the token:</p>
<ol>
<li><p>Locally, by using one of the many JWT libraries available.</p>
</li>
<li><p>By calling <a target="_blank" href="https://api.passwordless.id/#get-/openid/validate">/openid/validate</a></p>
</li>
</ol>
<p>That way, your servers and APIs can be sure of the user's identity. Each <code>id_token</code> will also possess a <code>sub</code> field according to the OpenID standard, which is a unique user identifier. You may also request the <code>email</code> as scope, but please be aware that an email is optional in Passwordless.ID, so you might not always obtain one even if you request it.</p>
<hr />
<h1 id="heading-its-only-an-alpha-version">It's only an "Alpha Version"!</h1>
<p>Passwordless.ID is not yet fully ready. It's at a proof-of-concept stage. Although it can already be used for testing and integration purposes, it is not yet quite ready for production.</p>
<p>If you think this is a meaningful public service and if you liked the tutorial, please like it and share it! The best way to make it succeed is to spread the word and encourage us. I hope you enjoyed it, I would be glad to hear your feedback!</p>
<p><img src="https://media4.giphy.com/media/L4fB9di7ekn3F5PXaW/giphy.gif?cid=ecf05e47447rfpemkli55zttugylznxlti2nh3nf194777gu&amp;rid=giphy.gif&amp;ct=g" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[The future of web authentication using touch / face / PIN is there!]]></title><description><![CDATA[Introduction
Web authentication through local device authentication like Android Touch ID, Windows Hello Face recognition or just a local device PIN code. This is now available for Browsers and offers not only a smoother user experience but is also m...]]></description><link>https://blog.passwordless.id/the-future-of-web-authentication</link><guid isPermaLink="true">https://blog.passwordless.id/the-future-of-web-authentication</guid><category><![CDATA[authentication]]></category><category><![CDATA[Passwordless]]></category><category><![CDATA[#webauthn]]></category><category><![CDATA[passkeys]]></category><category><![CDATA[OAuth2]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Tue, 10 Jan 2023 12:28:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673349672483/f4af822f-95ac-4239-8913-e958be0d40bd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Web authentication through local device authentication like Android Touch ID, Windows Hello Face recognition or just a local device PIN code. This is now available for Browsers and offers not only a smoother user experience but is also more secure.</p>
<p>It has many names: namely "Passwordless", "WebAuthn" and "PassKeys" but they all revolve around the same. It leverages a recent browser protocol called <a target="_blank" href="https://passwordless.id/protocols/webauthn/1_introduction">WebAuthn</a> which acts as a glue to invoke the local device authentication mechanism. Before going into details, let's see what it looks like (with my laptop/phone using a German locale).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672996003087/6579c34a-c18a-406f-b677-0baaefb58e10.png" alt class="image--center mx-auto" /></p>
<p>Looks great, right? Do you know anyone who likes passwords? Me neither. No more passwords == better UX. All those complex password rules, not reusing them, etc. is just annoying. And in the end, you forget the password anyway and must reset it.</p>
<p>Getting rid of passwords makes any registration/authentication much smoother and quicker. Moreover, it has several other benefits.</p>
<hr />
<h2 id="heading-more-secure">More secure</h2>
<h3 id="heading-better-than-passwords">Better than passwords</h3>
<p>It is <em>two-factor authentication</em> in a <em>single step</em>.</p>
<ul>
<li><p>The first factor is something you have. <em>The authentication only works on registered devices.</em></p>
</li>
<li><p>The second factor is something you are, or something you know. <em>A biometric verification, or the device PIN code, is required</em>.</p>
</li>
</ul>
<p>A combination of both is required to authenticate the user.</p>
<h3 id="heading-it-protects-from-phishing">It protects from phishing</h3>
<p>Phishing usually involves the user typing their password into a fake website. It is the most common way to hack accounts.</p>
<p>By getting rid of passwords, you get rid of phishing! Great, right?</p>
<p>Moreover, it also protects against further security threats like password reuse, social engineering attempts and the like.</p>
<h3 id="heading-it-protects-against-server-breaches">It protects against server breaches</h3>
<p>The underlying protocol relies on asymmetric cryptography. The private key is stored on the device while only the public key is known by the server. Even if the clear text data on the server is stolen, it is not sufficient to authenticate the user. The user's device platform itself must be hacked to do so.</p>
<hr />
<h2 id="heading-privacy-oriented">Privacy oriented</h2>
<h3 id="heading-your-fingerprintface-never-leaves-your-device">Your fingerprint/face never leaves your device</h3>
<p>Biometric verification uses the <strong><em>local</em></strong> <em>authentication</em> mechanism from your device. On the device, this biometric information is strongly protected and never exposed.</p>
<p>Verification is a safety measure used to prove you are you, then create or access cryptographic keys stored on your device. These keys, also known as passkeys, are in turn used for the authentication mechanism.</p>
<h3 id="heading-you-can-choose-not-to-use-fingerprintface">You can choose <em>not to use</em> fingerprint/face</h3>
<p>The user verification is delegated to your platform. If you are uncomfortable with such a mechanism, you can still use a PIN, a password, a swipe pattern configured, or whatever is configured on your device as the local authentication mechanism.</p>
<h3 id="heading-you-are-anonym">You are anonym</h3>
<p>The protocol does not reveal anything about you. It just receives randomly generated key pair IDs and public keys. Moreover, each website has its own set of credentials and has no idea about the credentials used by other websites.</p>
<hr />
<h2 id="heading-how-does-it-work-exactly">How does it work exactly?</h2>
<h3 id="heading-introduction-1">Introduction</h3>
<p>The authentication relies on a recent browser protocol called <a target="_blank" href="/protocols/webauthn/1_introduction">webauthn</a> which is based on <a target="_blank" href="https://en.m.wikipedia.org/wiki/Public-key_cryptography">asymmetric cryptography</a>.</p>
<p>During registration on a website, a pair of public and private keys is generated for the user. The private key is stored on the user’s device, while the public key is sent to the server.</p>
<p>When a user wants to authenticate themselves, they first request a challenge from the server. Then, they will reply with a message containing this challenge and signed using their private key.</p>
<p>The signature can lastly be verified by the server using the user’s public key obtained during registration. If the signature matches (and some other properties), the user is authenticated.</p>
<p>This simplified explanation of the protocol is illustrated by the following diagram.</p>
<p><img src="https://passwordless.id/protocols/webauthn/overview.svg" alt class="image--center mx-auto" /></p>
<h3 id="heading-show-me-the-code">Show me the code!</h3>
<p>To provide meaningful examples while avoiding the overhead of the native protocol, the following sections will make use of the <a target="_blank" href="https://github.com/passwordless-id/webauthn">@passwordless-id/webauthn</a> library, which is a high-level wrapper around the protocol.</p>
<p>It can be installed via <code>npm install @passwordless-id/webauthn</code> or imported directly in browser module scripts using <code>import { client } from 'https://unpkg.com/@passwordless-id/webauthn'</code></p>
<h3 id="heading-registration-browser-part">Registration (browser part)</h3>
<p>For registration, a cryptographic key pair must be generated for the user, it will be called a "credential" in the WebAuthn terminology. It can be created using the following call.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { client } <span class="hljs-keyword">from</span> <span class="hljs-string">'@passwordless-id/webauthn'</span> 

<span class="hljs-keyword">const</span> registration = <span class="hljs-keyword">await</span> client.register(<span class="hljs-string">"MyUsername"</span>, <span class="hljs-string">"some-random-generated-by-server"</span>)
</code></pre>
<p>For further options, check out the library's docs. Once the user confirms it using biometrics or PIN code, the key pair will be created, and the function will return. The private key is stored on the device, protected by local authentication, while the public key is returned.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"username"</span>: <span class="hljs-string">"MyUsername"</span>,
  <span class="hljs-attr">"credential"</span>: {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"3924HhJdJMy..."</span>,
    <span class="hljs-attr">"publicKey"</span>: <span class="hljs-string">"..."</span>,
    <span class="hljs-attr">"algorithm"</span>: <span class="hljs-string">"ES256"</span>
  },
  <span class="hljs-attr">"authenticatorData"</span>: <span class="hljs-string">"..."</span>,
  <span class="hljs-attr">"clientData"</span>: <span class="hljs-string">"..."</span>
}
</code></pre>
<p>This is a plain JSON object to be sent to the server. It contains various base64url encoded data that will first have to be parsed and verified.</p>
<h3 id="heading-registration-server-side">Registration (server side)</h3>
<p>Since the library also handles the server side, it can be used too there.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { server } <span class="hljs-keyword">from</span> <span class="hljs-string">'@passwordless-id/webauthn'</span> 

<span class="hljs-keyword">const</span> expected = {
    <span class="hljs-attr">challenge</span>: <span class="hljs-string">"whatever-was-randomly-generated-by-the-server"</span>,
    <span class="hljs-attr">origin</span>: <span class="hljs-string">"http://localhost:8080"</span>,
}
<span class="hljs-keyword">const</span> registrationParsed = <span class="hljs-keyword">await</span> server.verifyRegistration(registration, expected)
</code></pre>
<p>Either this operation fails and throws an Error, or the verification is successful and returns the parsed registration. Example result:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"username"</span>: <span class="hljs-string">"Arnaud"</span>,
  <span class="hljs-attr">"credential"</span>: {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"3924HhJdJMy..."</span>,
    <span class="hljs-attr">"publicKey"</span>: <span class="hljs-string">"..."</span>,
    <span class="hljs-attr">"algorithm"</span>: <span class="hljs-string">"ES256"</span>
  },
  <span class="hljs-attr">"authenticator"</span>: {
    ...
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Windows Hello Hardware Authenticator"</span>
  },
  ...
}
</code></pre>
<p>The registration is now considered complete.</p>
<p>What <em>must</em> be stored server side is:</p>
<ul>
<li><p>The <code>credential</code> (id, publicKey, algorithm)</p>
</li>
<li><p>Associate the <code>credential.id</code> to the user</p>
</li>
</ul>
<p>When the user wants to authenticate later, you will need the public key and algorithm to verify the signature. It is imperative to store it. In contrast, the <code>authenticator</code> part is optional. It delivers information about the authenticating device and is not verified.</p>
<h3 id="heading-authentication-browser-side">Authentication (browser side)</h3>
<p>When a user wants to authenticate themselves, they must sign a random "challenge" using their private key. This signature is sent to the server, which verifies it using the user's public key.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { client } <span class="hljs-keyword">from</span> <span class="hljs-string">'webauthn'</span>

<span class="hljs-keyword">const</span> authentication = <span class="hljs-keyword">await</span> webauthn.authenticate([<span class="hljs-string">"allowed-credential-id"</span>, <span class="hljs-string">"other-allowed-credential-id"</span>], <span class="hljs-string">"random-server-challenge"</span>)
</code></pre>
<p>Again, for all the available options, just consult the library's documentation. Once the user confirms the authentication attempt through biometric or PIN verification, the method returns the result.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"credentialId"</span>: <span class="hljs-string">"3924HhJdJMy..."</span>,
  <span class="hljs-attr">"authenticatorData"</span>: <span class="hljs-string">"..."</span>,
  <span class="hljs-attr">"clientData"</span>: <span class="hljs-string">"..."</span>,
  <span class="hljs-attr">"signature"</span>: <span class="hljs-string">"..."</span>
}
</code></pre>
<p>Unlike the registration, there is no credential object, just the <code>credentialId</code>. This JSON can be sent as it is to the server.</p>
<p>Please note that the username is not present. It is not part of the WebAuthn authentication protocol. Therefore, a mapping <code>credentialId -&gt; username</code> might be particularly useful to maintain server side. Alternatively, inject <code>username</code> in the JSON object before sending it.</p>
<h3 id="heading-authentication-server-side">Authentication (server side)</h3>
<p>The most important parts now are to:</p>
<ul>
<li><p>Verify the challenge</p>
</li>
<li><p>Verify the signature</p>
</li>
<li><p>Validate a few more "details"</p>
</li>
</ul>
<p>Given the <code>credentialId</code>, the server should possess the corresponding <code>publicKey</code> and <code>algorithm</code> used. These will be needed to verify the signature. The authentication verification procedure can be done with a single call.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { server } <span class="hljs-keyword">from</span> <span class="hljs-string">'@passwordless-id/webauthn'</span> 

<span class="hljs-comment">// obtained from database by looking up `authentication.credentialId`</span>
<span class="hljs-keyword">const</span> credentialKey = { 
    <span class="hljs-attr">id</span>: <span class="hljs-string">"3924HhJdJMy..."</span>,
    <span class="hljs-attr">publicKey</span>: <span class="hljs-string">"..."</span>,
    <span class="hljs-attr">algorithm</span>: <span class="hljs-string">"ES256"</span>
}

<span class="hljs-keyword">const</span> expected = {
    <span class="hljs-attr">challenge</span>: <span class="hljs-string">"whatever-was-generated-server-side"</span>,
    <span class="hljs-attr">origin</span>: <span class="hljs-string">"http://localhost:8080"</span>,
    <span class="hljs-attr">userVerified</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">counter</span>: <span class="hljs-number">0</span> 
}
</code></pre>
<p>Again, consult the documentation for all parameters. For example, a function predicate can be used for <code>challenge</code> and <code>origin</code> to make the verification more dynamic.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> authenticationParsed = <span class="hljs-keyword">await</span> server.verifyAuthentication(authentication, credentialKey, expected)
</code></pre>
<p>Either this operation fails and throws an <code>Error</code>, or the verification is successful and returns the parsed authentication payload.</p>
<p>Please note that this parsed result <code>authenticationParsed</code> has no real use. It is solely returned for the sake of completeness. The <code>verifyAuthentication</code> already verifies the payload, including the signature.</p>
<h3 id="heading-important-final-remarks"><em>Important</em> final remarks</h3>
<p>While it looks like an easy replacement for password based authentication. A bit more thought should be put into it.</p>
<p>Unlike traditional authentication systems that can be accessed from anywhere using a single password, authentication here is device bound. Losing a device means losing the private key used to sign in.</p>
<p>That the authentication is device bound affects many aspects of the user authentication:</p>
<ul>
<li><p>anonymous "usernameless" authentication becomes possible (where each device <em>is</em> its own account)</p>
</li>
<li><p>by default, relying on a single registered device also implies losing access completely if the device is lost/broken/stolen</p>
</li>
<li><p>registering multiple devices and being able to modify the list of allowed devices should be considered</p>
</li>
<li><p>more diverse account recovery options become possible, like through another registered device</p>
</li>
</ul>
<p>Taking care of all these aspects should not be underestimated. Especially if the goal is to replace traditional password based user authentication in an existing system.</p>
<hr />
<h2 id="heading-passwordlessid">Passwordless.ID</h2>
<h3 id="heading-the-vision">The vision</h3>
<p>Passwordless authentication comes with its fair share of complexity and gotchas. "<a target="_blank" href="http://passwordless.id">Passwordless.ID</a>" aims to solve this by being a "free public identity provider".</p>
<p>Its philosophy is simple.</p>
<ul>
<li><p>Make the web a safer place</p>
</li>
<li><p>Make it easier for developers</p>
</li>
<li><p>More comfort and control for users</p>
</li>
</ul>
<p>This is achieved by providing tools and services to delegate the authentication to the Passwordless.ID API.</p>
<h3 id="heading-oauh2-openid-compatible">OAuh2 / OpenID compatible</h3>
<p>Passwordless.ID is compatible with both <em>OAuth2</em> and <em>OpenID</em> protocols. That way, you can use it as a generic OpenID provider for a "Sign in with..." button.</p>
<p>If you are familiar with OAuth, you probably know that it is an "authorization" protocol. Usually, the API also offers a set of operations to grant permission to. In the case of Passwordless.ID, the only operation is accessing (part of) the user profile.</p>
<p>If you want to add Passwordless.ID as an additional social login provider using some predefined library, check out our <a target="_blank" href="/usage/openid">OAuth2/OpenID guide</a>!</p>
<h3 id="heading-sign-in-with">Sign in with...</h3>
<p>For a straightforward and smooth integration, you can use the <a target="_blank" href="https://github.com/passwordless-id/connect">@passwordless-id/connect</a> library. This library makes it possible to trigger the authentication/authorization using a single call.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673347475571/ccb9415f-eef1-4036-b2a5-9f88065ecf88.png" alt class="image--center mx-auto" /></p>
<p>Check out the <a target="_blank" href="https://github.com/passwordless-id/demo">standalone demo</a> to try it out and the <a target="_blank" href="https://passwordless.id/usage/sign-in-with">user guide</a> for implementing this on your site in 5 minutes! Not even an account is necessary!</p>
<h3 id="heading-shared-profile">Shared Profile</h3>
<p>Why would you need to register your profile information, portrait, address over and over for every website or app? It is simply annoying for everyone. Using this shared profile, any website or app can request read access to it. That way, the user has a single place to maintain the profile up to date.</p>
<p>The same goes for the list of authorized devices. It simply makes more sense to maintain it in one place.</p>
<h3 id="heading-free-and-public">Free and public</h3>
<p>Passwordless.ID is free for everyone and forever without a catch. It is our conviction that making it publicly available is the best way to make the internet a safer place as a whole.</p>
<p>To keep it striving in the long term, some funding is crucial. We would love to find some sponsors. If you are interested, please contact us or just drop a comment here.</p>
<h3 id="heading-empowering-the-users">Empowering the users</h3>
<p>Using Passwordless.ID, the privacy and security settings will be in the hands of the users instead of the websites and apps. The users will choose whether they stay logged in or not, what account recovery mechanisms they deem safe enough, what information they agree to share without explicit consent, and so on.</p>
<p>In particular, the last point is interesting. For example, a user might agree to share its nickname and portrait publicly. That way, any website could show its avatar in the corner without explicit authorization request beforehand.</p>
<h3 id="heading-currently-in-development"><em>Currently in development</em></h3>
<p>Passwordless.ID is not yet "finished". It is currently at the early proof of concept stage.</p>
<p>Although it lacks capabilities, it can already be used for testing and integration purposes. However, please note that the database might be reset at any point in the future until further notice.</p>
<hr />
<h2 id="heading-we-need-you">We need you!</h2>
<h3 id="heading-any-feedback-is-welcome">Any feedback is welcome</h3>
<p>This was made with love, sweat and considerate thoughts. We strive to make the best possible authentication experience and are glad to hear any feedback. Even a little "like" on this article is encouraging.</p>
<h3 id="heading-use-it">Use it</h3>
<p>This is meant to be a public identity provider. Help us make the web a better and safer place while making your developer's life easier. The best way is to use it. If you need help, just post an <a target="_blank" href="https://github.com/passwordless-id/www/issues">issue</a>. And if you already integrated it, we would be glad to feature you in a future blog article!</p>
<h3 id="heading-wanna-write-about-it">Wanna write about it?</h3>
<p>In case you plan to write a blog article, a tutorial, some news or anything alike, we would be glad to hear from you. Here are some <a target="_blank" href="https://github.com/passwordless-id/www/tree/main/logo">logos and banners</a> if you want. We might feature it on our blog too!</p>
<h3 id="heading-share-it"><strong>Share it</strong></h3>
<p>If you like it too, talk about it to others! Share it with someone! Every little act is of helps make it succeed. Thank you!</p>
<p>Some further links:</p>
<ul>
<li><p><a target="_blank" href="https://passwordless.id/">Passwordless.ID website</a></p>
</li>
<li><p><a target="_blank" href="https://passwordless-id.github.io/demo/">Sign in with Passwordless.ID demo</a></p>
</li>
<li><p><a target="_blank" href="https://passwordless.id/protocols/webauthn/1_introduction">Introduction to the WebAuthn protocol</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/passwordless-id/webauthn">High-level WebAuthn library</a></p>
</li>
<li><p><a target="_blank" href="https://webauthn.passwordless.id/demos/playground.html">WebAuthn library playground</a></p>
</li>
<li><p><a target="_blank" href="https://w3c.github.io/webauthn/">Official WebAuthn specifications</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[New "webauthn" library (web authentication through touch, swipe, face...)]]></title><description><![CDATA[Demo
Can registration/authentication be both smoother and more secure? Yes, it can! Without any password. This is sometimes called "webauthn", or "passkeys", or "passwordless authentication". It relies on the local platform authentication (using fing...]]></description><link>https://blog.passwordless.id/new-webauthn-library-web-authentication-through-touch-swipe-face</link><guid isPermaLink="true">https://blog.passwordless.id/new-webauthn-library-web-authentication-through-touch-swipe-face</guid><category><![CDATA[#webauthn]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Arnaud Dagnelies]]></dc:creator><pubDate>Sun, 04 Dec 2022 10:39:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670141828889/ftAp1XBXC.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-demo">Demo</h2>
<p>Can registration/authentication be both smoother and more secure? Yes, it can! <em>Without any password</em>. This is sometimes called "webauthn", or "passkeys", or "passwordless authentication". It relies on the local platform authentication (using fingerprint recognition, a swipe pattern, PIN code or whatever) and asymmetric cryptographic signatures to authenticate the user. This is quite a lot packed in a small sentence, it will be explained in more details later. First, let us take a look and experience it for yourself!</p>
<p>If you do not see the following demo in the iframe, you can check out directly https://passwordless.id which has the demo on top.</p>
<iframe height="700px" width="100%" src="https://webauthn.passwordless.id/demos/basic.html"></iframe>

<p>And just to avoid some misconceptions upfront...</p>
<p>The fingerprint, swipe pattern, face, etc. is never sent to the server, it's used for local authentication. Moreover  "passwordless" authentication is actually more secure than passwords since it is two-factor authentication by design</p>
<h2 id="heading-so-how-does-it-work-exactly">So how does it work exactly?</h2>
<p>This is based on the low-level <a target="_blank" href="https://www.w3.org/TR/webauthn/">webauthn</a> protocol, an official W3C standard implemented by all major platforms and browsers. For a more "digestible" version, I advise to look at the <a target="_blank" href="https://passwordless.id/webauthn/1_introduction">webauthn guide</a> explainers.</p>
<p>The flow looks like this:</p>
<p><img src="https://passwordless.id/webauthn/overview.svg" alt="flow" /></p>
<p>This flow diagram is of course very simplified, but it outlines the core principle. </p>
<p>During registration, a key pair is generated. This key pair, also called a passkey, is tied to the web domain (or a parent domain) and its access protected by the local platform authentication. The public key is sent to the server and later used to verify authentication attempts.</p>
<p>The private key is kept safe on the device. During authentication, the server sends a challenge, which is basically a nonce to avoid replay attacks, that the client must sign. This challenge can only be signed in the domain's context and requires the local platform authentication. The private key is never revealed at any point.</p>
<p>On the server side, the signature can be verified using the public key obtained during registration. Done. This also avoids a whole bunch of security vulnerabilities like password reuse, phishing, replay attacks, database breaches, etc. It's simply more secure by design.</p>
<h2 id="heading-the-library">The library</h2>
<p>The native protocol is fairly complex and low level. However, here is a library to make your life easier!</p>
<ul>
<li>NPM: https://www.npmjs.com/package/@passwordless-id/webauthn</li>
<li>GitHub: https://github.com/passwordless-id/webauthn</li>
</ul>
<p>So either install it per npm:</p>
<pre><code class="lang-bash">npm install @passwordless-id/webauthn
</code></pre>
<p>Or import it directly in your web page! In case you don't know it, by declaring a <code>&lt;script type="module"&gt;</code> on your page you can import modules directly on a plain html page. As an example, the first thing to do is to import the package and check if webauthn is available.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { client } <span class="hljs-keyword">from</span> <span class="hljs-string">'https://unpkg.com/@passwordless-id/webauthn'</span>

<span class="hljs-keyword">if</span>(client.isAvailable())
  alert(<span class="hljs-string">"Sorry, passwordless authentication is not yet available on your platform/browser!"</span>)
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<blockquote>
<p>Please note though that this does not work in codepen, jsfiddle or other such environment because you will be constrained by the iframe policy. You need to serve it directly, <em>localhost</em> is fine though.</p>
</blockquote>
<p>Likewise, it only works for chrome and edge. Safari has not yet been tested and firefox is not yet supported because they are a bit behind in their implementation, but that should change in the near future.</p>
<h2 id="heading-registration">Registration</h2>
<h3 id="heading-overview">Overview</h3>
<p>The registration process occurs in four steps:</p>
<ol>
<li>The browser requests a challenge from the server</li>
<li>The browser triggers <code>client.register(...)</code> and sends the result to the server</li>
<li>The server parses and verifies the registration payload</li>
<li>The server stores the credential key of this device for the user account</li>
</ol>
<p>Note that unlike traditionnal authentication, the credential key is attached to the device. Therefore, it might make sense for a single user account to have multiple credential keys.</p>
<h3 id="heading-1-requesting-challenge">1. Requesting challenge</h3>
<p>As explained previously, the challenge is basically a <a target="_blank" href="https://en.wikipedia.org/wiki/nonce">nonce</a> to avoid replay attacks.</p>
<pre><code><span class="hljs-keyword">const</span> challenge = <span class="hljs-comment">/* request it from server */</span>
</code></pre><p>Remember it on the server side during a certain amount of time and "consume" it once used.</p>
<h3 id="heading-2-trigger-registration-in-browser">2. Trigger registration in browser</h3>
<p>Example call:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { client } <span class="hljs-keyword">from</span> <span class="hljs-string">'@passwordless-id/webauthn'</span>

<span class="hljs-keyword">const</span> registration = <span class="hljs-keyword">await</span> client.register(<span class="hljs-string">"my-username"</span>, <span class="hljs-string">"randomly-generated-challenge-to-avoid-replay-attacks"</span>)
</code></pre>
<p>There are a few more options that you can set, but the default is just fine too.</p>
<p>Once the biometric or user input for local authentication is given, the function succeeds. The <code>registration</code> object that is obtained looks like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"username"</span>: <span class="hljs-string">"Arnaud"</span>,
  <span class="hljs-attr">"credential"</span>: {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"3924HhJdJMy_..."</span>,
    <span class="hljs-attr">"publicKey"</span>: <span class="hljs-string">"MFkwEwYHK..."</span>,
    <span class="hljs-attr">"algorithm"</span>: <span class="hljs-string">"ES256"</span>
  },
  <span class="hljs-attr">"authenticatorData"</span>: <span class="hljs-string">"SZYN5YgOjGh0NBcPZH...."</span>,
  <span class="hljs-attr">"clientData"</span>: <span class="hljs-string">"eyJ0eXBlIjoid2ViYX..."</span>
}
</code></pre>
<p>Then simply send this object as JSON to the server.</p>
<h3 id="heading-3-verify-it-server-side">3. Verify it server side</h3>
<p>The typescript library provides both sides, for the client and server. Simply import the corresponding module.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { server } <span class="hljs-keyword">from</span> <span class="hljs-string">'@passwordless-id/webauthn'</span> 

<span class="hljs-keyword">const</span> expected = {
    <span class="hljs-attr">challenge</span>: <span class="hljs-string">"random-challenge-to-avoid-replay-attacks"</span>,
    <span class="hljs-attr">origin</span>: <span class="hljs-string">"http://localhost:8080"</span>,
}
<span class="hljs-keyword">const</span> registrationParsed = <span class="hljs-keyword">await</span> server.verifyRegistration(registration, expected)
</code></pre>
<p>Either this operation fails and throws an Error, or the verification is successful and returns the parsed registration.
Example result:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"username"</span>: <span class="hljs-string">"Arnaud"</span>,
  <span class="hljs-attr">"credential"</span>: {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"3924HhJdJMy_..."</span>,
    <span class="hljs-attr">"publicKey"</span>: <span class="hljs-string">"MFkwEwYH..."</span>,
    <span class="hljs-attr">"algorithm"</span>: <span class="hljs-string">"ES256"</span>
  },
  <span class="hljs-attr">"authenticator"</span>: {
    ...
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Windows Hello Hardware Authenticator"</span>
  },
  ...
}
</code></pre>
<h3 id="heading-4-store-the-credential-key">4. Store the credential key</h3>
<p>The credential key is the most important part and should be stored in a database for later since it will be used to verify the authentication signature.</p>
<p>Please note that unlike traditional systems, a user might have multiple credential keys, one per device.</p>
<h2 id="heading-authentication">Authentication</h2>
<h3 id="heading-overview-1">Overview</h3>
<p>There are actually two kinds of authentications possible:</p>
<ol>
<li>By providing a list of allowed credential IDs</li>
<li>By letting the platform offer a default UI to select the user and its credential</li>
</ol>
<p>Both have their advantages and disadvantages that go beyond the scope of this tutorial.</p>
<p>The authentication procedure is similar to the procedure and divided in four steps.</p>
<ol>
<li>The browser requests a challenge from the server</li>
<li>The browser triggers <code>client.authenticate(...)</code> and sends the result to the server</li>
<li>The server loads the credential key used for authentication</li>
<li>The server parses and verifies the authentication payload</li>
</ol>
<h3 id="heading-1-requesting-challenge-1">1. Requesting challenge</h3>
<p>Again, the challenge is basically a <a target="_blank" href="https://en.wikipedia.org/wiki/nonce">nonce</a> to avoid replay attacks.</p>
<pre><code><span class="hljs-keyword">const</span> challenge = <span class="hljs-comment">/* request it from server */</span>
</code></pre><p>Remember it on the server side during a certain amount of time and "consume" it once used.</p>
<h3 id="heading-2-trigger-authentication-in-browser">2. Trigger authentication in browser</h3>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { client } <span class="hljs-keyword">from</span> <span class="hljs-string">'webauthn'</span>

<span class="hljs-keyword">const</span> authentication = <span class="hljs-keyword">await</span> webauthn.authenticate([<span class="hljs-string">"the-credential-id"</span>], <span class="hljs-string">"random-challenge-to-avoid-replay-attacks"</span>)
</code></pre>
<p>Like with the registration, we will omit any options since the defaults are just fine.</p>
<p>Once the biometric or user input for local authentication is given, the function succeeds and the obtained <code>authentication</code> object looks like this.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"credentialId"</span>: <span class="hljs-string">"3924HhJdJMy_..."</span>,
  <span class="hljs-attr">"authenticatorData"</span>: <span class="hljs-string">"SZYN5YgOjG..."</span>,
  <span class="hljs-attr">"clientData"</span>: <span class="hljs-string">"eyJ0eXBlIjoid2ViYXV0aG..."</span>,
  <span class="hljs-attr">"signature"</span>: <span class="hljs-string">"MEUCIAqtFVRrn7q9HvJC..."</span>
}
</code></pre>
<p>The signature is the important part here. It proves that the user possesses the corresponding private key.</p>
<h3 id="heading-3-in-the-server-load-the-credential-key">3. In the server, load the credential key</h3>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { server } <span class="hljs-keyword">from</span> <span class="hljs-string">'@passwordless-id/webauthn'</span> 

<span class="hljs-keyword">const</span> credentialKey = { <span class="hljs-comment">// obtained from database by looking up `authentication.credentialId`</span>
    <span class="hljs-attr">id</span>: <span class="hljs-string">"3924HhJdJMy_..."</span>,
    <span class="hljs-attr">publicKey</span>: <span class="hljs-string">"MFkwEwYHKoZIzj0CAQ...."</span>,
    <span class="hljs-attr">algorithm</span>: <span class="hljs-string">"ES256"</span>
} <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>

<span class="hljs-keyword">const</span> expected = {
    <span class="hljs-attr">challenge</span>: <span class="hljs-string">"random-challenge-to-avoid-replay-attacks"</span>,
    <span class="hljs-attr">origin</span>: <span class="hljs-string">"http://localhost:8080"</span>,
    <span class="hljs-attr">userVerified</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// should be set if `userVerification` was set to `required` in the authentication options (default)</span>
    <span class="hljs-attr">counter</span>: <span class="hljs-number">0</span> <span class="hljs-comment">// for enhanced security, you can store the number of times this authenticator was used and ensure it increases each time</span>
}
</code></pre>
<p>Often, it might also be more practical to use functions to verify challenge or origin. This is possible too:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> expected = {
    <span class="hljs-attr">challenge</span>: <span class="hljs-keyword">async</span> (challenge) =&gt; { <span class="hljs-comment">/* async call to DB for example */</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span> },
    <span class="hljs-attr">origin</span>: <span class="hljs-function">(<span class="hljs-params">origin</span>) =&gt;</span> listOfAllowedOrigins.includes(origin),
    <span class="hljs-attr">userVerified</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// no function allowed here</span>
    <span class="hljs-attr">counter</span>: <span class="hljs-number">0</span>  <span class="hljs-comment">// no function allowed here</span>
}
</code></pre>
<h3 id="heading-4-verify-the-authentication">4. Verify the authentication</h3>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> authenticationParsed = <span class="hljs-keyword">await</span> server.verifyAuthentication(authentication, credentialKey, expected)
</code></pre>
<p>Either this operation fails and throws an Error, or the verification is successful and returns the parsed authentication payload.</p>
<p>Please note that this parsed result <code>authenticationParsed</code> has no real use. It is solely returned for the sake of completeness. The <code>verifyAuthentication</code> already verifies the payload, including the signature.</p>
<h2 id="heading-done">Done 🎉</h2>
<p>Congratulations! Your user is now registered and authenticated using a more secure method! </p>
<p>You can also play around with the various options in the playground https://webauthn.passwordless.id/demos/playground.html, for example to use an external device. One of these options is for example to be able to select an extern authenticator, like an usb security key, or delegate authentication to your phone while using your laptop.</p>
<h2 id="heading-final-words">Final words</h2>
<p>Unlike traditional authentication systems, you must keep in mind that the credential key is device bound. (Well, this is partly true. Google, Microsoft and Apple plan to sync these "passkeys" just like they do with the passwords if you enable a setting like "synchronize device settings" or something similar.)</p>
<p>However, this requires you to think a bit differently about authentication. What if the user loses its device? How can a user register multiple devices? Can the user block a lost device?</p>
<p>Lastly, there might still be situations where the user's platform/browser combination does not properly support the webauthn protocol yet and requires fallback authentication mechanisms like plain old passwords or code links sent per e-mail / sms.</p>
<p>Therefore, this webauthn library solves part of the complexity by abstracting away all the low-level technical stuff. However the authentication system should be seen as a whole and there is still quite some complexity attached to it. Moreover, users would be annoyed to manage multiple profiles anyway. After all, why do I as a user, have to register a new account each time for every website / webapp? That is why <a target="_blank" href="https://passwordless.id">Passwordless.ID</a> is born (and is currently in development). It should provide a "Sign in with Passwordless.ID" button, which provides smoother user experience for the user, is super easy to user for developers and more secure for everyone as icing on the cake.</p>
<p>Thanks for reading, I'd love to hear your feedback!</p>
]]></content:encoded></item></channel></rss>