<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Shadowfacts</title>
    <link>https://shadowfacts.net</link>
    <description></description>
    <lastBuildDate>Tue, 02 Dec 2025 02:15:30 +0000</lastBuildDate>
    <item>
      <title>Custom Property Wrappers in SwiftUI</title>
      <link>https://shadowfacts.net/2025/swiftui-property-wrappers/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2025/swiftui-property-wrappers/</guid>
      <pubDate>Sun, 01 Jun 2025 03:55:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>You can use SwiftUI by trying to fight the framework or by trying to work <em>with</em> it. One of the best ways I’ve found of working with the framework is by using custom property wrappers. Of all the tools that SwiftUI provides, property wrappers are the one that most consistently makes me feel like I’m using the framework how it wants to be used.</p>
<!-- excerpt-end -->
<p>In Swift, <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/#Property-Wrappers">property wrappers</a> are simply types that are decorated with a special attribute and contain a <code>wrappedValue</code> member. While property wrappers by themselves can be useful, where they really shine is when combined with the tools SwiftUI provides for plumbing them into SwiftUI’s data model.</p>
<h2>Composing Property Wrappers</h2>
<p>The first step to building useful custom property wrappers is knowing that, like <code>View</code>s, property wrappers can themselves contain more property wrappers.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-attr">@<span class="hl-ty">propertyWrapper</span><span class="hl-rst"></span></span><span class="hl-kw">
struct </span>Uppercasing: <span class="hl-ty">DynamicProperty </span>{<span class="hl-attr">
    @<span class="hl-ty">Environment</span><span class="hl-rst">(\.<span class="hl-prop">myString</span>) </span></span><span class="hl-kw">var </span>myString: <span class="hl-ty">String</span><span class="hl-kw">

    var </span>wrappedValue: <span class="hl-ty">String </span>{<span class="hl-prop">
        myString</span>.<span class="hl-prop">uppercased</span>()
    }
}
</code></pre>
<p>The <code>DynamicProperty</code> protocol serves as a marker for SwiftUI that a conforming type may contain property wrappers which need to be plumbed into SwiftUI’s data model (similar to <code>View</code> itself). The example above works exactly like you’d expect.</p>
<p>The protocol also provides an <code>update</code> method which the documentation says is called “before rendering a view’s <code>body</code> to ensure the view has the most recent value.” This method serves as a useful point for ensuring that both the data you’re vending to the view is up-to-date and that any method for observing external changes is set up.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-attr">@<span class="hl-ty">propertyWrapper</span><span class="hl-rst"></span></span><span class="hl-kw">
struct </span>MyObserver: <span class="hl-ty">DynamicProperty </span>{<span class="hl-kw">
    var </span>index: <span class="hl-ty">Int</span><span class="hl-attr">

    @<span class="hl-ty">State </span><span class="hl-rst"></span></span><span class="hl-kw">var </span>wrappedValue: <span class="hl-ty">String</span><span class="hl-attr">
    @<span class="hl-ty">State </span><span class="hl-rst"></span></span><span class="hl-kw">private </span><span class="hl-kw">var </span>observer: <span class="hl-ty">AnyCancellable</span>?<span class="hl-kw">

    init</span>(index: <span class="hl-ty">Int</span>) {<span class="hl-prop"><span class="hl-kw">
        self</span></span>.<span class="hl-prop">wrappedValue </span>= <span class="hl-ty">MyManager</span>.<span class="hl-prop">shared</span>.<span class="hl-prop">values</span>[<span class="hl-prop">index</span>]
    }<span class="hl-kw">

    func </span>update() {<span class="hl-prop">
        observer </span>= <span class="hl-ty">MyManager</span>.<span class="hl-prop">shared</span>.<span class="hl-prop">addObserver</span>(<span class="hl-prop">index</span>) {<span class="hl-prop">
            wrappedValue </span>= <span class="hl-prop">$0</span>
        }
    }
}
</code></pre>
<h2>The Escape Hatch</h2>
<p>You can get pretty far by using the framework’s built-in property wrappers to actually observe data and trigger view updates. But there are plenty of scenarios where you may have some external change that you want to result in a view update too. What you need is some escape hatch to manually say to SwiftUI, “hey, my data has changed and my containing view should be updated.”</p>
<p>The way I most often reach for is by having, within my custom property wrapper, a <code>@StateObject</code> with an “observer” type that encapsulates the observation logic for the property wrapper. Having a single reference type to store state is useful in itself<sup class="footnote-reference"><a href="#1">1</a></sup>, but making it an <code>ObservableObject</code> means that you can trigger a view update just by calling <code>objectWillChange.send()</code>.</p>
<p>Another way you can do this without involving Combine is by giving your property wrapper its own <code>@State</code> value that contains some unused data whose only purpose is to be modified when the view needs to be updated. This approach is shown in <a href="https://saagarjha.com/blog/2024/02/27/making-friends-with-attributegraph/">this blog post</a> by Saagar Jha.</p>
<h2>Examples</h2>
<p>Finally, here are some examples of actual custom property wrappers I use, both in Tusker and in other projects, in hopes that they can provide some inspiration for ways custom property wrappers can be useful.</p>
<h3><a href="https://git.shadowfacts.net/shadowfacts/Tusker/src/commit/6f219c7c0847b97aab42c23b1712a8650be4b830/Packages/ComposeUI/Sources/ComposeUI/OptionalObservedObject.swift"><code>@OptionalObservedObject</code></a></h3>
<p>This one is pretty self-explanatory. SwiftUI’s <code>@ObservedObject</code> property wrapper doesn’t work with optional types. Mine functions exactly like you’d expect: it takes an optional value of a type that implements <code>ObservableObject</code> and, when it has a value, triggers a SwiftUI view update whenever the wrapped object changes.</p>
<p>The need for this one is largely obviated if you can exclusively target OS versions since the advent of <code>@Observable</code>, but it’s still useful if you can’t.</p>
<h3><a href="https://git.shadowfacts.net/shadowfacts/Tusker/src/commit/6f219c7c0847b97aab42c23b1712a8650be4b830/Packages/ComposeUI/Sources/ComposeUI/Views/WrappedProgressView.swift#L29"><code>@ProgressObserving</code></a></h3>
<p>This is a nice simple one. It uses <a href="https://developer.apple.com/documentation/swift/using-key-value-observing-in-swift">key-value observing</a> to watch a <code>Progress</code>’ <code>fractionCompleted</code> property and update a SwiftUI view whenever it changes. It also takes care of dispatching to the main thread if necessary (since a progress tree may be updated from multiple threads).</p>
<h3><a href="https://git.shadowfacts.net/shadowfacts/Tusker/src/commit/6f219c7c0847b97aab42c23b1712a8650be4b830/Packages/TuskerPreferences/Sources/TuskerPreferences/SwiftUI/PreferenceObserving.swift"><code>@PreferenceObserving</code></a></h3>
<p>A while back, I spent a bunch of time over-engineering a system for storing Tusker’s user preferences. The details of how it works aren’t germane (perhaps another time), but one important consideration is that all the preferences end up in a big <code>ObservableObject</code>. This means that, while watching the entire preferences object from SwiftUI is trivial, watching only a single preference key is somewhat awkward. This property wrapper encapsulates all that awkwardness, allowing a view to read the current value of a user preference and be updated whenever it changes (but not when irrelevant preferences change). The property wrapper is used by providing a <code>KeyPath</code> from the type of the preferences container object to the preference itself:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct </span>SomeView: <span class="hl-ty">View </span>{<span class="hl-attr">
    @<span class="hl-ty">PreferenceObserving</span><span class="hl-rst">(\.<span class="hl-prop">$statusContentMode</span>) </span></span><span class="hl-kw">var </span>statusContentMode<span class="hl-kw">
    var </span>body: <span class="hl-kw">some </span><span class="hl-ty">View </span>{ <span class="hl-cmt">/* ... */</span> }
}
</code></pre>
<h3><code>@EnvironmentOrLocal</code></h3>
<p>SwiftUI’s environment is great for dependency injection, but sometimes you need to override an environment value for just part of a view hierarchy, and trying to get the <code>.environment</code> modifier in the right place would be awkward. This is a cute one for a few reasons:</p>
<ol>
<li>The property wrapper is, itself, an <code>enum</code>. This works because SwiftUI correctly discovers and wires up the <code>Environment</code> associated value in one of the cases.</li>
<li>There’s an <code>.environment</code> static member, so that you don’t have to spell out the whole <code>Environment</code> initializer when the type can be inferred (e.g., in a view initializer’s default parameter).</li>
<li>Because of the magic of <code>@Observable</code>, nothing needs to be done to make SwiftUI’s dependency tracking work.</li>
</ol>
<details>
<summary>Expand for the full code</summary>
<pre class="highlight" data-lang="swift"><code><span class="hl-attr">@<span class="hl-ty">propertyWrapper</span><span class="hl-rst"></span></span><span class="hl-kw">
enum </span>EnvironmentOrLocal&lt;T: <span class="hl-ty">AnyObject </span>&amp; <span class="hl-ty">Observable</span>&gt;: <span class="hl-ty">DynamicProperty </span>{<span class="hl-kw">
    case </span>environment(<span class="hl-ty">Environment<span class="hl-rst">&lt;<span class="hl-ty">T</span>&gt;</span></span>)<span class="hl-kw">
    case </span>local(<span class="hl-ty">T</span>)<span class="hl-kw">

    static </span><span class="hl-kw">var </span>environment: <span class="hl-ty"><span class="hl-kw">Self </span></span>{
        .<span class="hl-prop">environment</span>(<span class="hl-ty">Environment</span>(<span class="hl-ty">T</span>.<span class="hl-prop"><span class="hl-kw">self</span></span>))
    }<span class="hl-kw">

    var </span>wrappedValue: <span class="hl-ty">T </span>{<span class="hl-kw">
        get </span>{<span class="hl-kw">
            switch </span><span class="hl-prop"><span class="hl-kw">self </span></span>{<span class="hl-kw">
            case </span>.<span class="hl-prop">environment</span>(<span class="hl-kw">let </span>env):<span class="hl-prop">
                env</span>.<span class="hl-prop">wrappedValue</span><span class="hl-kw">
            case </span>.<span class="hl-prop">local</span>(<span class="hl-kw">let </span>v):<span class="hl-prop">
                v</span>
            }
        }
    }
}
</code></pre></details>
<h3><a href="https://git.shadowfacts.net/shadowfacts/Tusker/src/commit/6f219c7c0847b97aab42c23b1712a8650be4b830/Packages/ComposeUI/Sources/ComposeUI/DraftObserving.swift#L11-L17"><code>@DraftObserving</code></a></h3>
<p>This is one of the more complex property wrappers that I’ve written for Tusker. In the app’s Compose screen, in-progress drafts are modeled with a CoreData object. In addition to the draft object itself, there are also several related objects, such as <code>DraftAttachment</code>s. Since <code>NSManagedObject</code> conforms to <code>ObservableObject</code>, it’s fairly straightforward to observe a single one. But there are a few places where I need to observe not only the draft, but also the various related objects<sup class="footnote-reference"><a href="#2">2</a></sup>. This property wrapper takes care of observing all of them and updating a SwiftUI whenever any changes. I won’t get into all the (slightly gnarly) details, but the key idea is that the property wrapper observes the <code>Draft</code> itself and, whenever the draft changes, checks whether any of the related objects have changed, attaching observers to them as well if needed.</p>
<h3><a href="https://git.shadowfacts.net/shadowfacts/Tusker/src/commit/6f219c7c0847b97aab42c23b1712a8650be4b830/Packages/ComposeUI/Sources/ComposeUI/DraftObserving.swift#L101"><code>@ThreadObserving</code></a></h3>
<p>Tusker allows composing an entire thread of posts at once and, like there are parts of the UI that may change whenever any aspect of a single draft changes, there are parts that may change whenever any aspect of <em>any</em> draft in the thread changes. This property wrapper is a fairly straightforward composition of many of the observer object that <code>@DraftObserving</code> uses, aggregating their effects together.</p>
<h3><a href="https://git.shadowfacts.net/shadowfacts/Tusker/src/commit/6f219c7c0847b97aab42c23b1712a8650be4b830/Packages/ComposeUI/Sources/ComposeUI/ComposeInput.swift#L81"><code>@FocusedInputAutocompleteState</code></a></h3>
<p>This one extends the focused input system I mentioned in my last post, about <a href="/2025/focused-values/"><code>FocusedValues</code></a>. One of the focused values used in Tusker’s Compose screen is a representation of the focused text input view itself (i.e., a <code>UITextView</code>). The actual protocol provides a number of properties, including a Combine publisher representing the state of the current autocomplete session (e.g., “inactive” or “mention starting with @sh”). It’s a publisher since its value changes over time and other parts of the UI need to be able to react to it, but the fact that it is makes observing it a little cumbersome.</p>
<p>This property wrapper uses <code>@FocusedValue</code> to get the current autocomplete state publisher and then attaches a subscriber to it. Then, whenever either the focused input changes or the autocomplete state of the current input changes, it triggers a SwiftUI view update.</p>
<div class="footnote-definition" id="1"><span class="footnote-definition-label">1. </span>
<p>Just like with a <code>View</code> update, you can’t modify <code>@State</code> values within a dynamic property’s <code>update</code> method, so it’s helpful to have a reference type in which you can stash things.</p>
</div><div class="footnote-definition" id="2"><span class="footnote-definition-label">2. </span>
<p>For example: depending on the user’s preferences, the validity of a draft overall may depend on whether or not all of the attachments have alt text.</p>
</div>]]></content:encoded>
    </item>
    <item>
      <title>FocusedValues in SwiftUI</title>
      <link>https://shadowfacts.net/2025/focused-values/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2025/focused-values/</guid>
      <pubDate>Sun, 11 May 2025 23:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>In my <a href="/2025/swiftui-preferences/">last post</a>, I wrote about using preferences in SwiftUI to make the shape of the graph do work for you. This post is something of an addendum to that, focused on focused values. We’ll look at how focused values work, and then see a few ways of taking advantage of them.</p>
<p>Here’s the one sentence explainer: the <code>FocusedValues</code> system is like preferences but where the reducer function is “whichever values originates from a focused view wins.”</p>
<p>And if that doesn’t sell you, here’s the pitch for people familiar with AppKit or UIKit: a useful way of thinking about the <code>FocusedValues</code> system is as a strongly typed, generalized version of the <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html#//apple_ref/doc/uid/10000060i-CH3-SW2">responder chain</a>.</p>
<!-- excerpt-end -->
<aside class="inline">
<p>By the way, the reference to the responder chain above is more than an analogy: because SwiftUI’s input controls are still backed by UIKit/AppKit under the hood, the <code>FocusedValues</code> system does actually use the responder chain. A custom representable view which is the first responder (or which itself contains a view that is the first responder) will work with the focused values system the same way as one of the framework’s builtin controls.</p>
</aside>
<p>Focused values are declared much like environment values. With a key type and a computed property, or the <code>@Entry</code> macro:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">extension </span><span class="hl-ty">FocusedValues </span>{<span class="hl-attr">
    @<span class="hl-ty">Entry </span><span class="hl-rst"></span></span><span class="hl-kw">var </span>myValue: <span class="hl-ty">Int</span>?
}
</code></pre>
<p>The main caveat they have is that the property’s type needs to be optional and its default value is always <code>nil</code>.</p>
<p>An actual value for a key is attached to a view using the <code>.focusedValue</code> modifier:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-ty">TextField</span>(<span class="hl-str">&quot;Username&quot;</span>, text: <span class="hl-prop">$username</span>)
	.<span class="hl-prop">focusedValue</span>(\.<span class="hl-prop">myValue</span>, <span class="hl-num">42</span>)
</code></pre>
<p>Whenever the <code>TextField</code> has focus, the value 42 will be written for the <code>myValue</code> key. This doesn’t just apply at the leaf nodes, though: you could attach the <code>.focusedValue</code> modifier to, say, a <code>VStack</code>, and that value would be written whenever <em>any</em> child of the <code>VStack</code> (or any child of their children, etc.) has focus.</p>
<aside class="inline">
<p>One way in which focused values diverge from preferences is that when a focused view and its ancestor both define a value for the same key, the <em>innermost</em> one wins. That’s in contrast to preferences, where the outermost value wins. To put it concretely: in the following example, the value for <code>myValue</code> that would be read is <code>42</code>:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-ty">TextField</span>(<span class="hl-str">&quot;Username&quot;</span>, text: <span class="hl-prop">$username</span>)
	.<span class="hl-prop">focusedValue</span>(\.<span class="hl-prop">myValue</span>, <span class="hl-num">42</span>)
	.<span class="hl-prop">focusedValue</span>(\.<span class="hl-prop">myValue</span>, <span class="hl-num">0</span>)
</code></pre></aside>
<p>Finally, reading a focused value takes place using the <code>@FocusedValue</code> property wrapper:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct </span>MyView: <span class="hl-ty">View </span>{<span class="hl-attr">
    @<span class="hl-ty">FocusedValue</span><span class="hl-rst">(\.<span class="hl-prop">myValue</span>) </span></span><span class="hl-kw">var </span>myValue: <span class="hl-ty">Int</span>?<span class="hl-kw">
    var </span>body: <span class="hl-kw">some </span><span class="hl-ty">View </span>{<span class="hl-kw">
        <span class="hl-cmt">// some view that contains the TextField above</span>
        let </span>_ = <span class="hl-prop">print</span>(<span class="hl-prop">myValue</span>) <span class="hl-cmt">// => Optional(42)</span>
	}
}
</code></pre>
<p>As is (hopefully) clear, the API for focused values is fairly straightforward. Next, let’s look at a few ways of using them effectively.</p>
<h2>Validate Menu Items</h2>
<p>Suppose you have menu commands which are <a href="https://developer.apple.com/documentation/swiftui/scene/commands(content:)">defined</a> at the scene level but whose enabled/disabled state depends on data from the currently edited object in your UI. You could try to hoist all the necessary state up to level where the menu command is added, or you could move into a <a href="https://chris.eidhof.nl/post/swiftui-view-model/">view model</a> or some other such object to make it easier to share. But better would be to define a focused value representing whether the command is enabled and read that from the commands content.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct </span>MyCommands: <span class="hl-ty">Commands </span>{<span class="hl-attr">
    @<span class="hl-ty">FocusedValue</span><span class="hl-rst">(\.<span class="hl-prop">myCommandEnabled</span>) </span></span><span class="hl-kw">var </span>myCommandEnabled: <span class="hl-ty">Bool</span>?<span class="hl-kw">
    var </span>body: <span class="hl-kw">some </span><span class="hl-ty">Commands </span>{<span class="hl-prop">
        CommandMenu</span>(<span class="hl-str">&quot;Editor&quot;</span>) {<span class="hl-prop">
            Button</span>(<span class="hl-str">&quot;Do Something&quot;</span>) {
                <span class="hl-cmt">// ...</span>
            }
            .<span class="hl-prop">disabled</span>(<span class="hl-prop">myCommandEnabled </span>!= <span class="hl-kw">true</span>)
        }
    }
}
</code></pre>
<h2>Literally Like the Responder Chain</h2>
<p>Take it one step beyond validating, and put the action itself into a focused value. The type of your focused value doesn’t have to be plain-old-data, it can be more complex. Your focused value could literally be the closure that’s used as the action for a menu item or a button elsewhere in your UI. By doing this, you’re effectively using the focused values system exactly as you would the responder chain.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct </span>MyCommands: <span class="hl-ty">Commands </span>{<span class="hl-attr">
    @<span class="hl-ty">FocusedValue</span><span class="hl-rst">(\.<span class="hl-prop">myAction</span>) </span></span><span class="hl-kw">var </span>myAction: (() -&gt; <span class="hl-ty">Void</span>)?<span class="hl-kw">
    var </span>body: <span class="hl-kw">some </span><span class="hl-ty">Commands </span>{<span class="hl-prop">
        CommandMenu</span>(<span class="hl-str">&quot;Editor&quot;</span>) {<span class="hl-prop">
            Button</span>(<span class="hl-str">&quot;Do Something&quot;</span>) {<span class="hl-prop">
                myAction</span>?()
            }
            .<span class="hl-prop">disabled</span>(<span class="hl-prop">myAction </span>== <span class="hl-kw">nil</span>)
		}
    }
}
</code></pre>
<h2>Sneakily Access a Platform View</h2>
<p>This one’s a little more niche, but I still find it useful. In Tusker’s Compose screen, there are multiple fields in which you can use custom emoji (using <code>:shortcodes:</code>), including the body of the post of course, but also the content warning, as well as any poll options. The Tusker UI includes a button to show the custom emoji picker, for when you can’t remember the shortcode. Implementing this is tricky since it relies on the state of the underlying UIKit text editing controls—namely, the cursor position (that being where a custom emoji should be inserted).</p>
<p>The way I tackle this is by using a focused value which holds a reference to the <code>UITextField</code>/<code>UITextView</code> itself. This requires a <a href="https://git.shadowfacts.net/shadowfacts/Tusker/src/commit/6f219c7c0847b97aab42c23b1712a8650be4b830/Packages/ComposeUI/Sources/ComposeUI/ComposeInput.swift#L52-L78">little bit</a> of <a href="https://git.shadowfacts.net/shadowfacts/Tusker/src/commit/6f219c7c0847b97aab42c23b1712a8650be4b830/Packages/ComposeUI/Sources/ComposeUI/Views/EmojiTextField.swift#L71-L77">ceremony</a>, but it means that, elsewhere, the UI has access to all the information it needs to make custom emoji autocomplete work smoothly.</p>
<p>The common thread among all of these suggestions is that, as with preferences, you’re using the structure of the graph that SwiftUI manages for you to carry information. The <code>.focusedValue</code> modifier tells you when something <em>inside</em> of it has focus, so take advantage of that rather than trying to convey the same information up the view hierarchy yourself.</p>
]]></content:encoded>
    </item>
    <item>
      <title>SwiftUI Preferences</title>
      <link>https://shadowfacts.net/2025/swiftui-preferences/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2025/swiftui-preferences/</guid>
      <pubDate>Tue, 22 Apr 2025 23:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Most data flow constructs in SwiftUI propagate data down the view tree. Preferences work in the opposite direction: passing data up the hierarchy. This makes them very powerful, both in ways that are fairly straightforward and by enabling more complex behavior.</p>
<!-- excerpt-end -->
<p>A preference is defined by two things: a default value, which is the value used when nothing writes an explicit value, and a reducer function, which defines how multiple values for the same preference at the same level of the view hierarchy are combined. The default value is fairly straightforward, and really depends on what, conceptually, the preference represents, so I won’t spend much time on it here. The reducer, however, is where the nuance lies.</p>
<h2>The Reducer Function</h2>
<p>When defining a <code>PreferenceKey</code>, one of the requirements of the protocol is the <code>reduce</code> method. Unfortunately, the documentation for the method isn’t great, and there’s one requirement in particular it has that’s not explicitly stated:</p>
<p><strong>It is incorrect to write <code>value = nextValue()</code> as your reducer.</strong></p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct </span>BadStringPref: <span class="hl-ty">PreferenceKey </span>{<span class="hl-kw">
    static </span><span class="hl-kw">var </span>defaultValue: <span class="hl-ty">String </span>{ <span class="hl-str">&quot;default&quot; </span>}<span class="hl-kw">
    static </span><span class="hl-kw">func </span>reduce(value: <span class="hl-kw">inout </span><span class="hl-ty">String</span>, nextValue: () -&gt; <span class="hl-ty">String</span>) {<span class="hl-prop">
        <span class="hl-cmt">// This is wrong!</span>
        value </span>= <span class="hl-prop">nextValue</span>()
    }
}
</code></pre>
<p>The reason for this is that, when the <code>reduce</code> method is called, either the current or next value may be the preference’s default value. That means that just naively overwriting the current value with the next one may replace a non-default preference value with the default one. What’s more, the <code>reduce</code> method may be called by SwiftUI even if there is only one explicitly-written preference in your view tree.</p>
<p>Here’s a simple example of a view hierarchy which can produce that result:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-ty">Color</span>.<span class="hl-prop">red</span>
    .<span class="hl-prop">preference</span>(key: <span class="hl-ty">BadStringPref</span>.<span class="hl-prop"><span class="hl-kw">self</span></span>, value: <span class="hl-str">&quot;non-default&quot;</span>)
    .<span class="hl-prop">overlay </span>{<span class="hl-kw">
        if </span><span class="hl-kw">true </span>{<span class="hl-prop">
            Color</span>.<span class="hl-prop">green</span>
        }
    }
	.<span class="hl-prop">onPreferenceChange</span>(<span class="hl-ty">BadStringPref</span>.<span class="hl-prop"><span class="hl-kw">self</span></span>) {<span class="hl-prop">
        print</span>(<span class="hl-prop">$0</span>) <span class="hl-cmt">// => "default"</span>
    }
</code></pre>
<p>The presence of the dynamic content (i.e., the <code>if</code> statement) inside the overlay<sup class="footnote-reference"><a href="#1">1</a></sup> results in the preference’s reduce method being called with a <code>nextValue</code> of the preference’s default. This can also happen with other modifiers, such as <code>toolbar</code>:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-ty">Color</span>.<span class="hl-prop">red</span>
    .<span class="hl-prop">preference</span>(key: <span class="hl-ty">BadStringPref</span>.<span class="hl-prop"><span class="hl-kw">self</span></span>, value: <span class="hl-str">&quot;non-default&quot;</span>)
    .<span class="hl-prop">toolbar </span>{
        <span class="hl-cmt">// Doesn't even need content!</span>
    }
	.<span class="hl-prop">onPreferenceChange</span>(<span class="hl-ty">BadStringPref</span>.<span class="hl-prop"><span class="hl-kw">self</span></span>) {<span class="hl-prop">
        print</span>(<span class="hl-prop">$0</span>) <span class="hl-cmt">// => "default"</span>
    }
</code></pre>
<p>So, how should this be fixed? Well, the <code>reduce</code> method needs to be modified to not return the default value if <em>either</em> the current or next values are non-default.<sup class="footnote-reference"><a href="#2">2</a></sup></p>
<p>More concretely, it depends on the type of the preference value. Optionals are probably the simplest case: the intuitive answer of “use the <code>??</code> operator” is correct. If you use <code>value = value ?? nextValue()</code>, then the only way the reduced value can be <code>nil</code> is if both current and next were <code>nil</code> to begin with. For booleans, you can use the logical OR or AND operators depending on whether the default value is <code>false</code> or <code>true</code>, respectively. Beyond that, it’s hard to make prescriptive recommendations since it depends on what the preference conceptually represents. The important part is that you need to take into consideration the current value, rather than always accepting the next value.</p>
<p>Stated more simply: while it’s more obviously wrong to always ignore the next value, it’s also incorrect to always ignore the current value.</p>
<h3>Reduce Order</h3>
<p>One more note about the <code>reduce</code> method: the docs say that it “receives its values in view-tree order.” But, <a href="https://mastodon.social/@harshil/114381961806317423">observationally</a>, this is not always the case<sup class="footnote-reference"><a href="#3">3</a></sup>. I think it would be more accurate for the docs to say that the method receives its values in “graph-order” (that is, the order in which the graph nodes representing the individual views are added), which often, but not necessarily always, matches the view-tree order. In any event, it seems prudent to avoid depending heavily on the order in which <code>reduce</code> sees preference values.</p>
<h2>Using Preferences Effectively</h2>
<p>SwiftUI models your user interface as a <a href="https://developer.apple.com/videos/play/wwdc2023/10156/?time=169">graph</a>, with each view and modifier being represented as a node in the graph. The way to get the most out of preferences is, in my experience, by using them to let the graph do work for you. In particular, this means that, instead of trying to build separate mechanisms that pass data up the hierarchy, just use preferences.</p>
<h3><code>ViewThatFits</code></h3>
<p>Suppose you have a design where you want to display two pieces of UI (information, controls, whatever) in one place if they both fit. If they don’t, however, you want to display only one piece of UI there, and move the other control elsewhere (e.g., into a menu). You could accomplish this by using <code>GeometryReader</code> and choosing a breakpoint. But you would have to lift the <code>GeometryReader</code> all the way up the hierarchy to the lowest common ancestor of the primary and secondary locations, which can have other implications for the layout.</p>
<p>Instead, you can use <code>ViewThatFits</code> to decide whether or not to display the secondary control, and use preferences to let the graph do the work of getting the information about which view was chosen to the alternate location for the secondary control.</p>
<p>One caveat with this approach lies in the way that <code>ViewThatFits</code> seems to work—at least as of iOS 18. <code>ViewThatFits</code> tries its children in the order that you specify them. But, while preferences of views after the one that’s selected are not written, the preferences of the views <em>before</em> the one selected (i.e., that are too big to fit) <em>are</em> written. This means that, to get the intended result of knowing which view is actually being displayed, you need to explicitly write a preference value (even if it’s the default value) for each view in the <code>ViewThatFits</code>:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-ty">ViewThatFits </span>{<span class="hl-prop">
    Color</span>.<span class="hl-prop">red</span>
    	.<span class="hl-prop">frame</span>(width: <span class="hl-num">200</span>)
    	.<span class="hl-prop">preference</span>(key: <span class="hl-ty">IsWide</span>.<span class="hl-prop"><span class="hl-kw">self</span></span>, value: <span class="hl-kw">true</span>)<span class="hl-prop">

    Color</span>.<span class="hl-prop">blue</span>
    	.<span class="hl-prop">preference</span>(key: <span class="hl-ty">IsWide</span>.<span class="hl-prop"><span class="hl-kw">self</span></span>, value: <span class="hl-kw">false</span>)
}
</code></pre>
<p>Notably, in the circumstances where, in the example above, the blue view is shown, the preference key’s <code>reduce</code> method is <em>not</em> invoked. This suggests to me that SwiftUI is taking into account the preferences written by <code>ViewThatFits</code> children to some degree, since the reducer would be called if two views were inside a different container.<sup class="footnote-reference"><a href="#4">4</a></sup></p>
<h3>Data Validation</h3>
<p>Here’s a more concrete example. In <a href="https://apps.apple.com/us/app/tusker/id1498334597">Tusker</a>, the Compose screen lets you author a thread of multiple posts together, before posting them all at once. Managing the state of this UI is a little complicated, because a thread can consist of an arbitrary number of draft posts, any one of which the user may be editing. While there are parts of the UI that only rely on the state of an individual draft, there are other parts (such as the “Post” button) that need to track the state of the entire thread. You might see where I’m going with this.</p>
<p>The view that’s responsible for editing an individual draft writes a preference whose value is whether or not that particular draft is valid (not exceeding the character limit, etc.)<sup class="footnote-reference"><a href="#5">5</a></sup>. The preference’s reducer function is the logical AND operator, and so when views higher in the hierarchy read the preference, SwiftUI has taken care of combining the individual drafts’ validities into a single value representing the state of the thread as a whole.</p>
<p>If you want to take a look at how this actually works in practice (it’s simpler than you might expect), <a href="https://git.shadowfacts.net/shadowfacts/Tusker/src/commit/6f219c7c0847b97aab42c23b1712a8650be4b830/Packages/ComposeUI/Sources/ComposeUI/Views/ThreadEditor.swift#L66-L107">the code</a> is public.<sup class="footnote-reference"><a href="#6">6</a></sup></p>
<p>This approach has the useful result that views that care about the validity of the entire thread do not need to somehow observe the state of a bunch of separate objects. The view layer’s observation of the model layer takes place at the natural granularity that emerges from the structure of the data. This is the crux of what I mean about letting SwiftUI’s graph do work for you. Primitives like <code>ForEach</code> influence the shape of the graph, which is something you can take advantage of.</p>
<div class="footnote-definition" id="1"><span class="footnote-definition-label">1. </span>
<p>This is not specific to the <code>overlay</code> modifier, but seemingly applies to any view that combines multiple child views, such as <code>VStack</code>.</p>
</div><div class="footnote-definition" id="2"><span class="footnote-definition-label">2. </span>
<p>In more math-y terms, you want the default value of the preference to be the identity value of the reduce operator.</p>
</div><div class="footnote-definition" id="3"><span class="footnote-definition-label">3. </span>
<p>Note that, in <a href="/2025/swiftui-preferences/list-preference.mp4">this screen recording</a>, when the <code>List</code> is scrolled up, lower numbers (which occur earlier in the view-tree order) are appended to the array, indicating that they are the <code>nextValue</code>.</p>
</div><div class="footnote-definition" id="4"><span class="footnote-definition-label">4. </span>
<p>Given that, I’m inclined to think the behavior described above is a bug (FB17310142), but fortunately one that’s easy to work around.</p>
</div><div class="footnote-definition" id="5"><span class="footnote-definition-label">5. </span>
<p>One could take this even further by having each control write a preference representing its own validity, but there’s a tradeoff with conceptual complexity.</p>
</div><div class="footnote-definition" id="6"><span class="footnote-definition-label">6. </span>
<p>There’s fair bit of unrelated stuff going on here—mostly related to some custom property wrappers and the actual validity checking for an individual draft—but, fundamentally, the preferences are being used exactly as described.</p>
</div>]]></content:encoded>
    </item>
    <item>
      <title>On AI</title>
      <link>https://shadowfacts.net/2025/ai/</link>
      <category>misc</category>
      <guid>https://shadowfacts.net/2025/ai/</guid>
      <pubDate>Sat, 22 Mar 2025 21:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I’ve been watching a lot of woodworking YouTube lately. The trope of software engineers taking up woodworking as a hobby is well-worn, and the consensus seems to be that its parallels to building software—combined with the physical tangibility—are attractive after spending so much time staring at a computer screen. But I don’t believe that explanation wholly captures the phenomenon.</p>
<p>I don’t think what you dislike about programming for a living is necessarily that you spend all day touching computers. I’ve seen burnout described as being a result of a disconnect between what you’re actually doing—day to day, hour to hour—and the aspect of your job that you feel is important and that you want to be doing. If you’re building a coffee table with your own two hands it’s harder to be disconnected from your work than if you’re spending all day developing a headache trying to explain why introducing a second, parallel onboarding flow for a small subset of users will only precipitate further headaches down the road.</p>
<!-- excerpt-end -->
<hr />
<p>I’ve also spent a lot of time thinking about “AI” lately. Not so much because I want to, but because its looming presence casts a pall over the entire field of programming. Certain segments of the industry are so bent on making fetch happen that thinking about it is simply inescapable.</p>
<p>Predictions about how AI will impact software development run the gamut from, at one extreme, “in five years, literally nobody will be writing code” to “LLMs will be treated as any other tool, like autocomplete” and, at the other extreme, “the software industry will have given up the pretense that AI-assisted software development provides any value.”</p>
<p>For my part, I land somewhere in the middle: while I have yet to have a truly positive experience with LLM-aided programming (admittedly, that’s mostly for a lack of trying—the reasons for which I’ll get to later), the technology does seem like it could produce useful results.</p>
<p>But, that said, I think we need to be clear that there are actually two separate axes here: how capital treats “AI” and what it actually is. I would like to live in a world where we don’t have to worry about the distinction. Unfortunately, this is not that world.</p>
<p>The way large language models are currently pitched is, fundamentally, a form of labor arbitrage. LLM tools are force multipliers. Everyone can be a 10× engineer with help from AI. A solo founder can crank out an entire MVP in a weekend. You have a new superpower. Go at it alone. Pay no attention to the unfathomable extent of the uncompensated-for labor behind the single most comprehensive work that humankind has ever produced. Substitute mere access to capital for the sum total of human effort required to produce <a href="https://www.robinsloan.com/lab/is-it-okay/">Everything</a>.</p>
<hr />
<p>To a first approximation, all of the furniture bought and sold these days is mass-produced. It doesn’t need to be particularly fancy or especially well-made. Being mass-produced and therefore widely available without being exorbitantly expensive is enough to succeed. I make no value judgement about that, it’s just the fact of the matter. Just as LLM maximalists want access to capital to displace knowledge work, access to capital (in the form of economies of scale) has killed the furniture trade.</p>
<p>Or has it? Like I said, I’ve been watching a lot of woodworking YouTube lately.</p>
<p>The artisinal furniture craft is still… alive. It would probably be an overstatement to say it still thrives. But it does live, on the efforts of people who pay more for furniture because they care about it, on the efforts of people who work slower and with more deliberation at the expense of greater profits. It lives, fundamentally, because of people who care about the <em>craft</em>.</p>
<hr />
<p>I said that I haven’t really made an effort to use LLM-based tools. There are two kinds of programming I do—work and not-work—and that is the case for both.</p>
<p>For work, I am in the fortunate position to not be in the sort of environment where output is valued above all else. I can work in the way that, well, works for me. And the way that works for me does not involve such tools. That’s not to say that using those kinds of tools couldn’t also work for me. But, right now, it doesn’t need to, and I have no particular desire to find out whether it does. There are things to be said about tying your career to your passions, but, for better or for worse, that’s where I am. I don’t need to use LLM tools to get my work done, and I care about how I get my work done, so I don’t use them. For not-work, it’s even simpler. I enjoy programming, and I don’t want to use tools that take that away from me.</p>
<p>I’m sorry to be the sort of person who’s going to make everything about class. But it only takes the slightest bit of class consciousness to see what’s happening, and where the industry is headed. To feel the <a href="https://www.reddit.com/r/LinkedInLunatics/search?q=ai&amp;restrict_sr=on&amp;sort=relevance&amp;t=all">glee</a> with which the petite bourgeoisie imagine firing 90% of their workforce. To hear the loathing for labor dripping from Sam Altman’s voice when he speaks of impending <a href="https://www.theatlantic.com/magazine/archive/2023/09/sam-altman-openai-chatgpt-gpt-4/674764/">job losses</a>. To see the <a href="https://www.businessinsider.com/microsoft-performance-based-job-cuts-have-started-termination-letters-2025-1">zeal</a> with which technology giants impose layoffs and simultaneously <a href="https://www.businessinsider.com/tech-industry-amazon-microsoft-meta-google-companies-intensity-hardcore-2025-3">bloviate</a> about how AI will massively increase worker productivity.</p>
<hr />
<p>So, after all of that, here is where I land:</p>
<p>I believe that people will continue to have the skills that capital claims AI will subsume. That I will continue to have those skills. That both of those will continue to be the case even as the labor market for software engineers contracts. I have to believe—I have to hope—because there is no other option. To be a pessimist is to cede your agency. I will not. The world is what we make of it and the world I want to make is one in which human labor is not entirely displaced by capital.</p>
<p>Even beyond that, I know that I care about building software and about making it good and about sweating the details. If there is no one left in the world programming for the sake of programming, it will be because I am dead. I will continue to care about the craft. Capital cannot take that away from me.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Version 7</title>
      <link>https://shadowfacts.net/2025/version-7/</link>
      <category>meta</category>
      <guid>https://shadowfacts.net/2025/version-7/</guid>
      <pubDate>Sat, 22 Feb 2025 17:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Welcome to version 7 of my website. What started as a rewrite of the backend last fall has since snowballed into a complete redesign of the frontend as well (if you’re using an RSS reader, check out the actual site). The previous design dates back to the fall of 2019 and, after doing a bunch of work to rewrite the backend, rebuilding the exact same frontend again wasn’t an attractive prospect.</p>
<!-- excerpt-end -->
<h2>Redesign</h2>
<p>I don’t really know what my design process is. This time, I spent ages trawling through hundreds of other people’s personal blogs and websites trying to find anything that I liked, hoping that something cohesive would eventually emerge. It ultimately did, though I have no idea wherefrom.</p>
<h3>Typography</h3>
<p>The body text is set in <a href="https://mbtype.com/fonts/valkyrie/">Valkyrie</a> by Matthew Butterick, and the code is set in <a href="https://mass-driver.com/typefaces/md-io/"><code>MD IO</code></a> by Mass Driver. Valkyrie is a font that I’ve had my eye on for a while, since reading Butterick’s <a href="https://practicaltypography.com"><em>Practical Typography</em></a>. In the old design, I deliberately chose to use a system font (<span style="font-family: Charter, Georgia, serif;">Charter</span>) to avoid the (over-the-wire byte) cost of shipping an entire font. This time, though, I put quite a lot of work into the backend static-site-generator in part so that I could ship <a href="#font-subsetting">subset fonts</a> without a lot of manual work.</p>
<p>It took me a while to settle on <code>MD IO</code> since it’s not one of my usual monospace fonts. But I’m quite happy with it ultimately. It looks nice with Valkyrie when used inline, and looks good in bigger blocks of code. It also has a nice <em><code>italic</code></em> style, which is important to me since comments in syntax-highlighted code blocks are italicized. I’m very pleased with how the font choices turned out, I think they both look great—especially on high-DPI displays<sup class="footnote-reference"><a href="#1">1</a></sup>.</p>
<h3>Markdown Decorations</h3>
<p>One of the more notable touches from the <a href="/2019/reincarnation/#new-theme">old design</a> was the use of CSS pseudo-elements to render inline Markdown decorations (i.e., underscores around italicized text, making links appear like <code>[hello](example.com)</code>, etc.). I still quite like that touch, but after five and a half years, it’s time to move on. No more Markdown decorations—but maybe they’ll come back in a future redesign.</p>
<h3>Sidenotes</h3>
<p>One of the side effects of having the content be width-constrained and left-aligned is that I have this big column of empty space on the right to play with. The previous design already used floating asides on wide enough screens, and I wanted to extend that to footnotes as well. I’m the type of person who reads all the footnotes, and having to jump to the end back is somewhat jarring—even if there are backlinks, I often visually lose my place just from the viewport changing. So, sidenotes it is.</p>
<p>I embarked on an exhaustive survey of someone else’s <a href="https://gwern.net/sidenote">exhaustive survey</a> of existing web libraries for creating sidenotes. Unfortunately, just about all of the options either had limitations that I wasn’t willing to accept (e.g., not supporting sidenotes that are numbered like footnotes) or were entirely reliant on JavaScript.</p>
<p>The solution that I arrived at is a custom combination of some backend processing in the static site generator and a bunch of CSS. When footnotes in Markdown are being converted to HTML, the reference to and the contents of every footnote is output twice: first immediately following the footnote reference in an inline <code>&lt;span&gt;</code> element, and then again in the footnotes section at the end. Having the sidenote definition inline immediately after the reference lets me do two things with CSS alone:</p>
<ol>
<li>Align the sidenote to exactly where it’s referenced in the content.</li>
<li>Highlight the sidenote when the corresponding reference is hovered.</li>
</ol>
<p>The second set of definitions are at the end, and are only shown when the screen is viewport is narrow enough for the sidenotes not to fit. Lastly, the two sets of references exist because—although the text of the links is the same—the destinations are different: one jumps to the footnotes section at the end of the page, the other to the sidenote. Which reference is shown is also swapped out using CSS, this time producing a purely functional change since they’re visually identical.</p>
<h3>Color Schemes</h3>
<p>For a couple reasons, there’s no longer a dark theme. What you see now is what there is. The first reason is that I’ve never particularly enjoyed light text on a dark background for reading—I always preferred the light theme of my old design. The second is that, since the page background is now a nice off-white, I found no satisfying way of incorporating that into a dark theme.</p>
<h2>The Graph</h2>
<p>The impetus for rewriting the backend of my blog can be summed up in one word: graph. Specifically, a dependency graph.</p>
<p>I should back up first. When building the backends for the last couple versions of my blog, I’ve done things pretty much the same way: write a bunch of code that reads files and outputs more files. Less a cohesive piece of software, and more of a script. No real consideration was made for incremental compilation or watching files for changes. At various times, this was handled by <a href="https://github.com/eradman/entr">separate tools</a> and by just watching the entire content directory for changes and rebuilding the whole site. This did work, but it was less than ideal—both because it bothers the perfectionist in me and because regeneration gets inexorably slower as the blog grows.</p>
<p>So, the path I went down was building the entire static site generator around a giant directed, acyclic graph. Each node in the grah is a unit of computation that produces an output value from the values of some input nodes (or inputs external to the graph, like the filesystem). Powering this <del>monstrosity</del> elegant architecture is a Rust crate I wrote called <code>compute_graph</code> which abstracts away dealing with the actual graph and lets you just write code for each of the nodes.</p>
<p>Actually generating the site, then, is effectively just (“just”) topologically sorting the graph and evaluating each node in order. From there, it’s not terribly complicated to track which files are read during the process and, when the filesystem watcher detects a change, invalidate <em>just</em> the nodes that read that file and let the graph evaluation take care of the rest. Because the graph also prevents invalidations from propagating downstream when a node’s value doesn’t change, partial updates can be much more efficient than just regenerating things willy-nilly (e.g., editing the excerpt of a blog post will regenerate both the post page and the homepage, but editing not-the-excerpt won’t regenerate the homepage).</p>
<p>One of the more interesting details of the graph abstraction is that the output value of a node can, itself, be <em>more</em> graph nodes. This means that part of the process of graph evaluation is going back, making sure all the edges are established, then redoing the topological sort and starting the evaluation again.</p>
<p>And, for fun, here’s a diagram of the entire dependency graph—all 977 nodes—of the website as of writing this (you really do want to be in a browser for this, it likely won’t work in your RSS reader):</p>
<style>
#svg-container {
    overflow: scroll;
    height: 500px;
    background: white;
}
</style>
<figure>
    <div id="svg-container">
        <object type="image/svg+xml" data="/2025/version-7/graph.svg" id="svg"></object>
    </div>
    <figcaption>
        <code id="svg-label"></code>
        You can scroll, zoom, and click nodes to see connected edges and node labels.
        <br>
        <a href="#" id="zoom-out">Zoom Out</a>.
        <a href="#" id="zoom-in">Zoom In</a>.
    </figcaption>
</figure>
<script src="/2025/version-7/graph.js" defer></script>
<h3>Font Subsetting</h3>
<p>Font subsetting is one of the big features that the whole graph abstraction enables. Subsetting is the process of creating a new font that contains only a subset of the characters/glyphs that the original font has. The simplest way to do this would be do determine what character set you’re interested in based on the language the content is written in, and generate a subset font for that.</p>
<p>If the entire site is typeset in just one style of one font, that process would work fine. But are you also using all of those characters in the bold style of the font? The bold italic one? The monospace bold italic one<sup class="footnote-reference"><a href="#2">2</a></sup>? Probably not.</p>
<p>So here’s where the big dependency graph comes in: every node that writes an HTML file to disk also outputs the HTML as a string that’s used by further nodes to extract the set of distinct characters used from each font style. This process involves parsing the HTML, walking the tree while keeping track of the current font state (to handle nesting), and accumulating the set of characters. The character sets from each file are merged and then those are inputs to the node responsible for actually subsetting the font. Subsetting is performed by <a href="https://fonttools.readthedocs.io/en/latest/subset/index.html">pyftsubset</a> from the fontTools project<sup class="footnote-reference"><a href="#3">3</a></sup> which is run directly from Rust using <a href="https://pyo3.rs/">PyO3</a>. Finally, the subset fonts are Base 64 encoded and embedded in the compiled CSS.</p>
<p>The graph is very useful for this whole thing because it means that everything that needs to be regenerated is whenever necessary, and only when necessary (i.e., editing content in a way that doesn’t add new characters to the fonts doesn’t re-subset them), without having to write a bunch of code to explicitly keep track of all that information.</p>
<h2>ActivityPub</h2>
<p>One of the major features of <a href="/2019/reincarnation/#activity-pub">version 5</a> was making my blog a first-class ActivityPub citizen. This meant you could follow my blog from Mastodon (&amp; co.) and leave comments by replying. This was a cool feature, but I’ve made the decision to leave it behind this time. It was too great of a code/maintenance burden for too few users. While it didn’t break too often, it accounted for the majority of the code in v6 and made deployment somewhat more complicated, since it wasn’t just a static site—which now it is.</p>
<p>I haven’t entirely abandoned fediverse integration though: now you can comment on blog posts by replying via ActivityPub to a post from my personal account. I think this is a good middle ground: it’s effectively delegating all the ActivityPub responsibility to <a href="https://akkoma.dev">Akkoma</a>, which is actually built for that purpose first and foremost—rather than my hodgepodge—and simply fetching them from the client side. I was also already posting links to blog posts, and almost all of the responses I’d get are directly to that.</p>
<div class="footnote-definition" id="1"><span class="footnote-definition-label">1. </span>
<p>Having put so much time into choosing fonts, I remain disappointed in the state of the external monitor market. My white whale of a 5K, high-DPI, high refresh rate monitor remains elusive. The type on this website looks perfectly good on a regular display, but it looks so much better in high-DPI (like even the screen on my laptop).</p>
</div><div class="footnote-definition" id="2"><span class="footnote-definition-label">2. </span>
<p>In fact, I’m currently not using the monospace bold italic variant at all. And part of the process about to be described asserts that that is the case and will prevent the site from generating if that changes.</p>
</div><div class="footnote-definition" id="3"><span class="footnote-definition-label">3. </span>
<p>I would like to be able to subset web fonts directly from Rust, but there are no existing projects that do so. I started writing my own—and got as far as parsing WOFF2—but switched to the Python project in the interest of expediency. To be continued…</p>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Belief</title>
      <link>https://shadowfacts.net/2025/belief/</link>
      <category>politics</category>
      <guid>https://shadowfacts.net/2025/belief/</guid>
      <pubDate>Sat, 01 Feb 2025 17:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I do not pretend to know fully understand how this came to be the case, or what to do about it, but I am increasingly of the opinion that the single most pressing problem of the American polity is that of amorality. Not immorality, <em>amorality</em>. It is not the case that we collectively believe what we are doing to be wrong—or believe it to be right; we simply do not care.</p>
<p>We are too eager to look for a way out of a hard situation, too eager to just do the easy thing, too happy to cast out of our minds what does not directly affect us (or what we want to believe will not directly affect us).</p>
<!-- excerpt-end -->
<p>See, for example:</p>
<ol>
<li>The Fourth Estate rolling over when faced with a protection racket.</li>
<li>Hospitals abandoning caring for trans and pregnant patients.</li>
<li>Too much of legal academia being willing to carry water for abhorrent positions (e.g., the birth and slow mainstreaming of the atextual and ahistorical opposition to birthright citizenship).</li>
<li>The Harris campaign being unwilling to shift from Biden on any major issues.</li>
<li>Merrick Garland preserving norms at the Justice Department.</li>
<li>Democrats in general continuing to try and do politics as normal.</li>
</ol>
<p>This is not an exhaustive list; you could surely add more.</p>
<p>Take just one of those, for example. If you were to get in a room with the executives at Paramount contemplating settling Trump’s 60 Minutes lawsuit, and wave a magic wand to get them to bare their souls—or if some alien could examine their innermost thoughts—I think that they would profess their belief that a free and independent press is integral to a well-functioning society. It is such a core part of the American mythos that there is little doubt in my mind that those Paramount executives do not think they believe it is the case.</p>
<p>But, I would posit that to believe something is to act in accordance with it. A professed belief with which you do not act in accordance is not something you actually believe.</p>
<blockquote>
<p>The Electric Monk was a labour-saving device, like a dishwasher or a video recorder. Dishwashers washed tedious dishes for you, thus saving you the bother of washing them yourself, video recorders watched tedious television for you, thus saving you the bother of looking at it yourself; Electric Monks believed things for you, thus saving you what was becoming an increasingly onerous task, that of believing all the things the world expected you to believe.</p>
<p>— Douglas Adams, <em>Dirk Gently’s Holistic Detective Agency</em></p>
</blockquote>
<p>I believe that there are right things and that there are wrong things. And I fervently believe it is incumbent upon each of us to do what we believe is right, even when (or, perhaps, especially when) doing so comes at a personal cost. I only wish that I knew how to instill those values into a culture.</p>
<p>Inextricably bound up in all this is the notion that conservatives are unreachable because <a href="https://www.huffpost.com/entry/i-dont-know-how-to-explain-to-you-that-you-should_b_59519811e4b0f078efd98440">“I don’t know how to explain to you that you should care about other people”</a>. Caring about what happens to other people has the necessary condition of believing that there are right things and wrong things. If you do not believe that, then all that matters—and all that <em>can</em> matter—is whether something that happens is good for you or bad for you.</p>
<p>For example: conservatives not caring about abortion until someone close to them needs one. If you do not believe that there are right things and wrong things—if you do not believe that pregnant women dying of sepsis in hospital parking lots is a wrong thing, then until the pregnant woman dying of sepsis in a hospital parking lot is your wife or your daughter, it simply does not matter.</p>
<p>Furthermore, if belief lies in the acts, then the hostility towards and dismantling of the social safety net is a deliberate to make it harder to act in accordance with your beliefs and to make it harder to believe.</p>
<p>Nonetheless, I need you to <em>believe</em> that there are right things and that there are wrong things.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Your View&apos;s Lifetime Is Not Yours</title>
      <link>https://shadowfacts.net/2024/swiftui-lifecycle/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2024/swiftui-lifecycle/</guid>
      <pubDate>Sat, 30 Nov 2024 23:41:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>When SwiftUI was announced in 2019 (oh boy, more than 5 years ago), one of the big things that the Apple engineers emphasized was that a SwiftUI <code>View</code> is not like a UIKit/AppKit view. As Apple was at pains to note, SwiftUI may evaluate the <code>body</code> property of a <code>View</code> arbitrarily often. It’s easy to miss an important consequence this has, though: your <code>View</code> will also be initialized arbitrarily often. This is why SwiftUI views are supposed to be structs which are simple value types and can be stack-allocated: constructing a <code>View</code> needs to be cheap.</p>
<p>More precisely, what I mean by the title of this post is that the lifetime of a struct that conforms to <code>View</code> is unmoored from that of the conceptual thing representing a piece of your user interface.</p>
<!-- excerpt-end -->
<aside class="inline">
<p>A note on definitions: in this post, I’m using the word ‘lifecycle’ and ‘lifetime’ in very particular ways. Lifecycle is connected to the conceptual view, and encompasses things like the <code>onAppear</code>/<code>onDisappear</code> modifiers. Lifetime, however, is a property of a value: the time from its initialization to its destruction (that is, how long the value exists in memory).</p>
</aside>
<p>This comes up on social media with some regularity. Every few months there’ll be questions about (either directly, or in the form of questions that boil down to) why some view’s initializer is being called more than expected. One particularly common case is people migrating from Combine/<code>ObservableObject</code> to the iOS 17+ <code>@Observable</code>.</p>
<p>If you were not paying attention to SwiftUI in the early days, or have only started learning it more recently, there are some important details about the nature of the framework you might have missed. One of the things the SwiftUI engineers emphasized when it was announced was that the <code>body</code> property can be called at any time by the framework and may be called as often as the framework likes. What follows from this—that’s not entirely obvious or always explicitly stated—is that <code>View</code> initializers run with the same frequency. Consider the minimal possible case:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct </span>MyParentView: <span class="hl-ty">View </span>{<span class="hl-kw">
    var </span>body: <span class="hl-kw">some </span><span class="hl-ty">View </span>{<span class="hl-prop">
        MyChildView</span>()
    }
}
</code></pre>
<p>Evaluating <code>MyParentView</code>’s body necessarily means running the initializer for <code>MyChildView</code>. SwiftUI does a lot of work to make things feel magical, but, at the end of the day, the code that you’re writing is ultimately actually running. So, the initializer runs.</p>
<p>After that happens, SwiftUI can compare the new and old values of <code>MyChildView</code> to decide whether it’s changed and, in turn, whether to read its body. But, by the time the framework is making that decision, the initializer has already been run.</p>
<h2>An Autoclosure Corollary</h2>
<p>It follows, I argue, from the reasons given above that it is a poor API design choice for SwiftUI property wrappers to use <code>@autoclosure</code> parameters in the wrapped value initializer.</p>
<p>An entirely reasonable principle of API design is that you should hide unnecessary complexity from consumers of your API. There is, however, a very fine line between hiding unnecessary complexity and obscuring necessary complexity—or worse, being actively misleading about what’s happening. I think <code>@StateObject</code> and its autoclosure initializer tread too close to that line.</p>
<p>The use of an autoclosure hides the fact that the property wrapper itself is getting initialized frequently—just like the view that contains it (initializing the view necessarily means initializing all of its properties—i.e., the property wrappers themselves). If your API design is such that you can very easily write two pieces code that, on the surface, look almost identical but have dramatically different performance characteristics, then it hinders (especially inexperienced) developers’ understanding.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-attr">@<span class="hl-ty">StateObject </span><span class="hl-rst"></span></span><span class="hl-kw">var </span>model = <span class="hl-ty">MyViewModel</span>()<span class="hl-attr">
<span class="hl-cmt">// vs.</span>
@<span class="hl-ty">State </span><span class="hl-rst"></span></span><span class="hl-kw">var </span>model = <span class="hl-ty">MyViewModel</span>()
</code></pre>
<p>That the name <code>StateObject</code> conflates the behavior with <code>State</code> certainly doesn’t help either.</p>
<h2>Some Recommendations</h2>
<p>Not realizing that view initializers—not just the <code>body</code> property—are in the hot path can quickly get you into hot water, particularly if you’re doing any expensive work or are trying to use RAII<sup class="footnote-reference"><a href="#1">1</a></sup> with a value in a view property. So, what follows are some general recommendations for avoiding those issues that are as close to universally correct as I’m willing to claim in writing.</p>
<h3>No Expensive Work in Inits</h3>
<p>This is a simple one. As discussed earlier, this is equivalent to doing that same expensive work in a view’s <code>body</code> property. But SwiftUI calls both of those at arbitrary times with arbitrary frequency. Doing expensive work in either place will quickly cause performance issues.</p>
<h3>Ignore <code>View</code> Lifetimes</h3>
<p>Even more generally, you should ignore the lifetime of your <code>View</code> structs altogether. “When is my view initialized? Who knows, I don’t care!” Even if, at some point during development, you find that a particular view’s initializer is called at a useful time, avoid depending on that, even if the work you need to do is cheap. While, right now, you might have a good handle on a particular <code>View</code>’s lifetime, it’s very easy to inadvertently change that in the future, either by introducing additional dependencies in the view’s parent, due to changes to the <a href="https://developer.apple.com/videos/play/wwdc2021/10022/">view’s identity</a>, or due to entirely framework-internal changes.</p>
<p>Note that this is a particularly important idea and is worth hammering home: a <code>View</code>’s initializer is called as part of its <em>parent’s</em> <code>body</code>. This is particularly prone to action at a distance: seemingly-unrelated changes at the callsite where a view is used (or beyond) can dramatically change how often that view is initialized.</p>
<h3>Use Lifecycle Modifiers</h3>
<p>The <a href="https://developer.apple.com/documentation/swiftui/state#Store-observable-objects"><code>@State</code> docs</a> call this out with regard to <code>Observable</code>, but it’s true in general. Rather than putting expensive objects as the wrapped value of a <code>@State</code> property wrapper, use SwiftUI’s lifecycle modifiers like <code>.onAppear</code> or <code>.task</code> to construct them.</p>
<p>What’s more, if you need to do any one-time setup work—even inexpensive—related to a particular instance of a <em>conceptual</em> view, the lifecycle modifiers are the way to go. They abstract you away from the particularities of the lifetime of the struct instance itself.</p>
<div class="footnote-definition" id="1"><span class="footnote-definition-label">1. </span>
<p>Resource Acquisition Is Initialization: the idea that the lifetime of a value is tied to some other resource (e.g., an open file). The wrapped value of a <code>@State</code> property is evaluated every time the containing view is initialized, so doing any resource acquisition work therein can be expensive.</p>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Parsing HTML Slower</title>
      <link>https://shadowfacts.net/2024/parsing-html-slower/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2024/parsing-html-slower/</guid>
      <pubDate>Wed, 22 May 2024 23:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p><a href="/2023/parsing-html-fast/">Last time</a>, I wrote about how to parse HTML and convert it to <code>NSAttributedString</code>s quickly. Unfortunately, in the time since then, it’s gotten slower. It’s still a good deal faster than it was before all that work, mind you. At fault is not any of the optimizations I discussed last time, fortunately. Rather, to get the correct behavior across a whole slew of edge cases, there was more work that needed to be done.</p>
<p>The root of all this complexity is the fact that I’m essentially trying to replicate a portion of the CSS layout algorithm using only the information provided by the HTML tokenization process (that is, the text that is emitted and the start/end tags) while flattening into a single string all the structure used to achieve those results.</p>
<!-- excerpt-end -->
<p>The previous version of this—which did correctly handle the initial test cases that I threw at it, but not what cropped up in the wild—worked by trying to keep track of when you had just finished one block element and then, before starting a new one, emitting line breaks to approximate the spacing between them that would otherwise be specified by CSS. Here are an assortment of issues that arise when using this strategy with real input:</p>
<h3>Blocks can start after a closing non-block element</h3>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">span</span>&gt;a&lt;/<span class="hl-tag">span</span>&gt;&lt;<span class="hl-tag">p</span>&gt;b&lt;/<span class="hl-tag">p</span>&gt;
</code></pre>
<p>This should render equivalent to two blocks, the first containing “a” and the second containing “b”.</p>
<h3>Blocks can start immediately after a character</h3>
<pre class="highlight" data-lang="html"><code>a&lt;<span class="hl-tag">p</span>&gt;b&lt;/<span class="hl-tag">p</span>&gt;
</code></pre>
<p>This should render the same as the example above.</p>
<h3>Whitespace between blocks should be ignored</h3>
<p>That is, this:</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">p</span>&gt;a&lt;/<span class="hl-tag">p</span>&gt;&lt;<span class="hl-tag">p</span>&gt;b&lt;/<span class="hl-tag">p</span>&gt;
</code></pre>
<p>should render equivalent to this:</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">p</span>&gt;a&lt;/<span class="hl-tag">p</span>&gt;
&lt;<span class="hl-tag">p</span>&gt;b&lt;/<span class="hl-tag">p</span>&gt;
</code></pre>
<p>which in turn should render equivalent to this:</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">p</span>&gt;a&lt;/<span class="hl-tag">p</span>&gt;



&lt;<span class="hl-tag">p</span>&gt;b&lt;/<span class="hl-tag">p</span>&gt;
</code></pre>
<p>So you need to keep track of the whitespace that was emitted between two block elements, and not emit any extra—or possibly remove some.</p>
<h3>Whitespace within blocks should not be ignored</h3>
<p>But you can’t ignore whitespace (or even just newlines) altogether, because there is content that expects newlines to be preserved. And, moreover, people will do things like writing code—where they expect whitespace to be preserved—outside of a <code>&lt;pre&gt;</code> context.</p>
<h3>Empty block elements shouldn’t take up space</h3>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">p</span>&gt;a&lt;/<span class="hl-tag">p</span>&gt;
&lt;<span class="hl-tag">p</span>&gt;&lt;/<span class="hl-tag">p</span>&gt;
&lt;<span class="hl-tag">p</span>&gt;b&lt;/<span class="hl-tag">p</span>&gt;
</code></pre>
<p>This should render equivalently to the examples above with two <code>&lt;div&gt;</code>s, since the emtpy tag shouldn’t consume any space. Additionally, other empty elements inside that middle <code>&lt;div&gt;</code> shouldn’t cause it to start consuming space either.</p>
<h3>A line break tag at the end of a block should be ignored</h3>
<p>So while this example should appear to have three line breaks between “a” and “b”:</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">p</span>&gt;a&lt;/<span class="hl-tag">p</span>&gt;
&lt;<span class="hl-tag">p</span>&gt;&lt;<span class="hl-tag">br</span>&gt;b&lt;/<span class="hl-tag">p</span>&gt;
</code></pre>
<p>this next example should only appear to have two:</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">p</span>&gt;a&lt;<span class="hl-tag">br</span>&gt;&lt;/<span class="hl-tag">p</span>&gt;
&lt;<span class="hl-tag">p</span>&gt;b&lt;/<span class="hl-tag">p</span>&gt;
</code></pre>
<h3>But a line break tag at the beginning of a block should take up space</h3>
<p>This example should have about three lines worth of space between “a” and “b”. One for the space between “a” and the middle paragraph, one for the <code>&lt;br&gt;</code> content of the middle paragraph, and one for the space between the middle paragraph and “b”.</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">p</span>&gt;a&lt;/<span class="hl-tag">p</span>&gt;
&lt;<span class="hl-tag">p</span>&gt;&lt;<span class="hl-tag">br</span>&gt;&lt;/<span class="hl-tag">p</span>&gt;
&lt;<span class="hl-tag">p</span>&gt;b&lt;/<span class="hl-tag">p</span>&gt;
</code></pre>
<h3>Actual newline characters should be ignored at the beginning and end of blocks</h3>
<p>So all three of these paragraphs are equivalent:</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">p</span>&gt;a&lt;/<span class="hl-tag">p</span>&gt;
&lt;<span class="hl-tag">p</span>&gt;a
&lt;/<span class="hl-tag">p</span>&gt;
&lt;<span class="hl-tag">p</span>&gt;
a&lt;/<span class="hl-tag">p</span>&gt;
</code></pre>
<h3>Leading whitespace in <code>&lt;pre&gt;</code> is ignored</h3>
<p>Presumably because, when authoring HTML, you’re expected to write the opening tag on one line and then start your content on the next.</p>
<p>Observationally, this also appears to be the case for whitespace preceding <code>&lt;/pre&gt;</code>, but the HTML spec is silent on the matter.</p>
<h3>List items are like blocks but with only one linebreak</h3>
<p>In this case, there should be no blank lines between “a” and “b”:</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">ul</span>&gt;
    &lt;<span class="hl-tag">li</span>&gt;a&lt;/<span class="hl-tag">li</span>&gt;
    &lt;<span class="hl-tag">li</span>&gt;b&lt;/<span class="hl-tag">li</span>&gt;
&lt;/<span class="hl-tag">ul</span>&gt;
</code></pre>
<h3>Blocks in <code>&lt;li&gt;</code> should not produce line breaks at the beginning/end of the list item</h3>
<p>It’s perfectly okay to have block elements inside list items, but the following should render the same as the previous example:</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">ul</span>&gt;
    &lt;<span class="hl-tag">li</span>&gt;&lt;<span class="hl-tag">p</span>&gt;a&lt;/<span class="hl-tag">p</span>&gt;&lt;/<span class="hl-tag">li</span>&gt;
    &lt;<span class="hl-tag">li</span>&gt;&lt;<span class="hl-tag">p</span>&gt;b&lt;/<span class="hl-tag">p</span>&gt;&lt;/<span class="hl-tag">li</span>&gt;
&lt;/<span class="hl-tag">ul</span>&gt;
</code></pre>
<h3>But two blocks within the same <code>&lt;li&gt;</code> should have a blank line separating them</h3>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">ul</span>&gt;
    &lt;<span class="hl-tag">li</span>&gt;&lt;<span class="hl-tag">p</span>&gt;a&lt;/<span class="hl-tag">p</span>&gt;&lt;<span class="hl-tag">p</span>&gt;b&lt;/<span class="hl-tag">p</span>&gt;&lt;/<span class="hl-tag">li</span>&gt;
&lt;/<span class="hl-tag">ul</span>&gt;
</code></pre>
<h2>Okay, I’m going insane. How do you fix it?</h2>
<p>After trying to make my initial approach work for some time, while slowly discovering more and more of these edge cases, I gave up. Armed with the intuition that this feels like the sort of problem that should be tractable with a sufficiently complicated state machine, I spent a number of days drawing, abandoning, and redrawing diagrams in Freeform trying to cover all the inputs and properties I needed.</p>
<p>In addition to everything described above, the final version also covers a couple additional things such as nested <code>&lt;pre&gt;</code> tags (because why not) and removing (or preventing from being generated) leading/trailing whitespace from the output string.</p>
<p>With the diagram converted to code (and several intervening iterations of noticing issues and revising the diagram), I am left with a gnarly 600 lines of Swift consisting of 7 giant <code>switch</code> statements, each of which matches over the 20 distinct states of the machine.</p>
<p>The code by itself is nigh-impossible to reason about, which is why it bears the warning “This is not a place of honor. No highly esteeme—” er. I mean, which is why it bears the exhortation:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-cmt">// DO NOT TOUCH THE CODE WITHOUT CHECKING/UPDATING THE DIAGRAM.</span>
</code></pre>
<p>Speaking of the diagram: in an effort to preserve the sanity of future-me, I turned my chicken-scratch drawing into a GraphViz file that I could stick in version control. Here’s the rendered graph, in all its splendor (please write in with suggestions about how to make this not look like GraphViz is questioning its life choices):</p>
<figure>
    <img src="/2024/parsing-html-slower/block-state-diagram.png" alt="A graph showing a state machine with 20 states and more transitions than I can count.">
    <figcaption>You may wish to <a href="/2024/parsing-html-slower/block-state-diagram.png" data-link="/2024/parsing-html-slower/block-state-diagram.png" target="_blank">zoom in</a> and pan around.</figcaption>
</figure>
<h2>Conclusion</h2>
<p>In writing this blog post I ran into <del>1</del> 2 more edge cases that I had not handled correctly.</p>
<p>While I don’t regret rewriting the HTML parsing/conversion code I had before, it does feel  rather like having opened Pandora’s box.</p>
<p>And back to what I alluded to at the beginning, all this extra bookkeeping means the end-to-end HTML → attributed string conversion performance is now merely 2.3× faster than the old SwiftSoup-based implemention (as opposed to 2.7× faster before this whole state machine adventure).</p>
]]></content:encoded>
    </item>
    <item>
      <title>Parsing HTML Fast</title>
      <link>https://shadowfacts.net/2023/parsing-html-fast/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2023/parsing-html-fast/</guid>
      <pubDate>Wed, 27 Dec 2023 23:52:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>The urge to overengineer something recently befell me, and the target of that urge was the way Tusker handles HTML parsing. Mastodon provides the content of posts (and profile descriptions, and some other things) as HTML, which means that, in order to display them properly, you need to parse the HTML, a notoriously straightforward and easy task. For a long time, the approach I took was to use <a href="https://github.com/scinfu/SwiftSoup">SwiftSoup</a> to parse the HTML and then walk the resulting node tree to build up a big <code>NSAttributedString</code> that could be displayed.</p>
<p>And while that technique worked perfectly well, there were a number of downsides that I wasn’t entirely satisfied with. First: SwiftSoup alone is a <em>big</em> dependency, at around ten thousand lines of code. Since it’s shipping in my app, I take the view that I’m responsible for making sure it doesn’t break—and that’s a lot of surface area to cover (moreover, since it was ported from a Java library, the code is extremely un-Swift-y). Second: it’s not especially fast. Partly because of its aforementioned nature as a Java port (everything is a class, lots of unnecessary copies), but primarily because it’s just doing more than I need it to (the first rule of optimization is do less work): it was parsing a string into a tree and then I was flattening that tree back into a string.</p>
<p>The <a href="/2022/swift-rust/">last time</a> I needed to do something similar, I used lol-html, Cloudflare’s streaming HTML rewriter. But, while that worked (and is still in use for my RSS reader app), it’s written in Rust, and I didn’t want to introduce significant additional complexity to the build process for Tusker. So, writing a new HTML parser in Swift seemed like a great idea.</p>
<!-- excerpt-end -->
<h2>The Architecture</h2>
<p>Parsing HTML is no small task, and <a href="https://html.spec.whatwg.org/multipage/parsing.html">the spec</a> stands at some 42 thousand words. Fortunately, I get to ignore about half of that. Parsing HTML is divided into two primary stages: tokenizing and tree construction. The tokenization stage takes the character stream as input and produces a stream of tokens (text characters, comments, start/end tags, etc.). The tree construction stages then uses the token stream to build up the actual DOM tree. The main way I’m improving over SwiftSoup in terms of not doing unnecessary work is by skipping tree construction altogether.</p>
<p>This is based on lol-html’s architecture, which usually uses a simulated tree construction stage (because the tokenizer gets feedback from the tree constructor and may have its state altered based on the current position in the tree) and only switches to the slower, real one when necessary. In my case, though, I don’t even simulate tree construction, since everything I want from the output can be generated directly from the token stream.</p>
<p>This does mean that in certain circumstances the output of the tokenizer could be incorrect, but that’s not a problem for my use case. For Tusker, I’m only ever going to use it to parse fragments—not whole documents—and the Mastodon backend sanitizes incoming HTML. What’s more, from what I can tell of lol-html’s simulator, most of the cases where (without the tree construction stage) ambiguity arises or feedback is needed aren’t going to come up in my use case. They involve other tag namespaces (SVG, MathML), or things like <code>&lt;template&gt;</code> tags—both of which I’m comfortable with handling incorrectly.</p>
<h3>Tokenizing</h3>
<p>The tokenizer is the single biggest component of this project, weighing in at about 1500 lines of code. Fortunately though, the spec is quite clear. It describes in exacting detail the state machine that a spec-compliant tokenizer needs to mimic. To make things simple, all of my code is an almost one-for-one translation of the spec into Swift. There are a few places where I diverged, mostly in the name of performance, but where I didn’t, the code is quite easy to follow.</p>
<h3>Attributed Strings</h3>
<p>Converting the token stream to an <code>NSAttributedString</code> is somewhat more complicated, but not terribly so. The gist of it is that the converter tracks what the style of the string should be at the current point in the token stream. Then, when a text character arrives from the tokenizer, it’s added to a buffer representing characters for which the current styles apply (called a “run”). When an open/close tag token is emitted, the current style is adjusted. After the current styles change, the current run is finished and an attributed string is built for the current text and styles and is appended to the overall string.</p>
<h2>Optimization</h2>
<h3>Named Character References</h3>
<p>The spec requires HTML tokenizers to have some special handling for named character references (things like <code>&amp;amp;</code>) which aren’t well-formed (e.g., missing the trailing semicolon). Because I was sticking very closely to the spec, this was originally implemented rather inefficiently: when a named character reference began, it would consume further characters one by one while there were any named character references beginning with the accumulated characters. I was just using a regular Swift dictionary to store all of the valid character references, so this was effectively an O(n) operation for each character in the reference.</p>
<p>That is, to put it mildly, ridiculously inefficient. Instead, the new approach just consumes as many alphanumeric characters as possible until it hits a semicolon. Then, if there’s no valid character reference, it backs up character by character until it finds a valid reference. This optimizes for the common case of a properly-formed reference, but (as far as I can tell) preserves the error-handling behavior that the spec requires.</p>
<h3>Static Dispatch</h3>
<p>The way I implemented the tokenizer, there’s a separate method that follows the procedure for each state, each of which returns the next token. The tokenizer itself is also an <code>Iterator</code> that yields tokens. The <code>Iterator.next</code> implementation just switches over the current state and dispatches to the correct method.</p>
<p>It’s fairly common for the procedure for tokenizing to involve switching to a different state. Originally, I did this by changing the <code>state</code> and then calling <code>next()</code>. One small optimization I made relatively early was, in instances where the new state is constant, replacing the <code>next()</code> call with a direct call to the appropriate method for the new state—effectively statically dispatching whenever the state changes.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-prop">state </span>= .<span class="hl-prop">tagName</span><span class="hl-kw">
return </span><span class="hl-prop">next</span>()<span class="hl-prop">
<span class="hl-cmt">// vs.</span>
state </span>= .<span class="hl-prop">tagName</span><span class="hl-kw">
return </span><span class="hl-prop">tokenizeTagName</span>()
</code></pre>
<h3>UI/NSFont Caching</h3>
<p>A number of the tags handled by the attributed string converter alter the current font. Originally, I implemented this by looking at the currently-applied styles and using them to assemble a <code>SymbolicTraits</code> that could be passed to <code>UIFontDescriptor.withSymbolicTraits</code> on the base font descriptor. When profiling, though, I found a non-trivial amount of time was being spent inside <code>withSymbolicTraits</code> to look up the actual font that should be used. So, the converter instead caches the known styles and their corresponding font objects.</p>
<h3>Using <code>Swift.Array</code></h3>
<p>One of the first optimizations I’d implemented was a type called <code>InlineArray3</code> which was a collection type that stored up to three elements inline. Growing beyond that would cause it to switch to a regular Swift array, the storage for which is out of line. The motivation for this was the fact that the vast majority of the HTML tags that the parser sees will have very few attributes, so you might be able to gain a little bit of performance by storing them inline.</p>
<p>After implementing a simple performance testing harness (discussed later), I went back and tested this, and it turned out that just using <code>Array</code> was a fair bit faster. Just goes to show that you shouldn’t try to optimize things without having concrete data.</p>
<h3>Looping in Hot Recursive Functions</h3>
<p>For a number of states in the tokenizer (such as while tokenizing a tag name), the state machines stays in the same state for multiple iterations before emitting a token. Initially, I implemented this just by having the tokenization function recursively call itself. One small but measurable performance improvement I got came from turning hot recursive paths into loops. So, rather than the function calling itself recursively, the entire function body would be wrapped in an infinite loop and the “recursive call” would simply continue on to the next loop iteration.</p>
<p>This was one of the more surprising optimizations I made, since I’d expected this to make no difference. Given tail-call optimization, I’d think both approaches would generate basically identical code. And indeed, looking at the disassembly before and after this change seems to confirm that. There are differences, and though they seem very minor, I guess they’re enough to make a difference (though a very, very small one—only about 5ms over 10k iterations).</p>
<h3>Avoiding Enums with Associated Values</h3>
<p>Lots of states of the tokenizer involve building up the current token until it’s ready to emit. This means the parser needs to store the in-progress token. My <code>Token</code> data type is a Swift enum with a handful of different cases. So, the natural way of representing the current token was as an instance variable of an optional <code>Token</code>. Modifying the current token means, then, pattern matching against a <code>Token</code> and then constructing the updated <code>Token</code> and assigning it back.</p>
<p>Unfortunately, this does not permit in-place modification, meaning that all of the enum’s associated values need to be copied. For primitive types, this might not be a problem. But for more complex types, like arrays and strings, this results in a bunch of extra copies. So, rather than having a single <code>currentToken</code> property, there are separate properties representing the data for each possible token—each of which can be modified in-place:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">private </span><span class="hl-kw">var </span>currentToken: <span class="hl-ty">Token</span>?<span class="hl-kw">
<span class="hl-cmt">// vs.</span>
private </span><span class="hl-kw">var </span>currentStartTag: (<span class="hl-ty">String</span>, selfClosing: <span class="hl-ty">Bool</span>, attributes: [<span class="hl-ty">Attribute</span>])?<span class="hl-kw">
private </span><span class="hl-kw">var </span>currentEndTag: <span class="hl-ty">String</span>?
<span class="hl-cmt">// etc.</span>
</code></pre>
<h3>Using <code>Unicode.Scalar</code> instead of <code>Character</code></h3>
<p>My tokenizer originally operated on a stream of Swift <code>Character</code>s, which represent extended grapheme clusters. The tokenizer, however, only cares about individual Unicode code points—and indeed is specified to operate on code points. So, I changed the tokenizer to operate on the <code>Unicode.Scalar</code> type. This resulted in a significant speed-up, since the there was no longer any time being spent on the grapheme breaking algorithm.</p>
<h3>Grouping Character Tokens</h3>
<p>When emitting the document text, the tokenizer is specified to emit individual characters. As an optimization, though, I introduced an additional token type which emits a whole string of characters in one go. Since long, uninterrupted runs of characters that don’t require further parsing are quite common in actual HTML documents, this eliminates a bunch of the overhead associated with handling a token that comes from switching back and forth between the tokenizer and whatever’s consuming the tokens (that overhead is amortized over the sequence of character tokens).</p>
<h2>Conclusion</h2>
<p>To both test the final performance of the new end-to-end HTML to <code>NSAttributedString</code> conversion process as well as guide most of the optimizations I described above, I wrote a small benchmarking harness that converts the HTML from the last ten thousand posts on my timeline to attributed strings using the old and new methods. This is basically the exact use case I have for this project, so I’m confident that the benchmarking results are fairly representative.</p>
<p>All told, the new process is about 2.7× faster when both are built in release mode, and a whopping 8× faster in debug (not terribly important, but nice for me since a debug build is often what’s on my phone).</p>
<p>I’m very happy with how this project turned out. I greatly enjoyed overengineering/optimizing something fairly self-contained, and the end result meets all of the goals/requirements I had for my use case. If you’re curious to see how it works in more detail, check out <a href="https://git.shadowfacts.net/shadowfacts/HTMLStreamer">the source code</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Passkey Sign In with Elixir and Phoenix</title>
      <link>https://shadowfacts.net/2023/phoenix-passkeys/</link>
      <category>elixir</category>
      <guid>https://shadowfacts.net/2023/phoenix-passkeys/</guid>
      <pubDate>Thu, 19 Oct 2023 15:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Passkeys are a replacement for passwords that use public/private cryptographic key pairs for login in a way that can be more user-friendly and resistant to a number of kinds of attacks. Let’s implement account registration and login with passkeys in a simple Phoenix web app.</p>
<p>This work is heavily based on <a href="https://www.imperialviolet.org/2022/09/22/passkeys.html">this article</a> by Adam Langley, which provides a great deal of information about implementing passkeys. My goal here is to fill in some more of the details, and provide some Elixir-specific information. As with Adam’s article, I’m not going to use any WebAuthn libraries (even though that may be advisable from a security/maintenance perspective) since I think it’s interesting and helpful to understand how things actually work.</p>
<p>Providing an exhaustive, production-ready implementation is a non-goal of this post. I’m going to make some slightly odd decisions for pedagogical reasons, and leave some things incomplete. That said, I’ll try to note when I’m doing so.</p>
<!-- excerpt-end -->
<p>To start, I’m using the default Phoenix template app (less Tailwind) in which I’ve also generated the default, controller-based <a href="https://hexdocs.pm/phoenix/mix_phx_gen_auth.html">authentication system</a>. Some parts of the password-specific stuff have been stripped out altogether, others will get changed to fit with the passkey authentication setup we’re going to build.</p>
<h2>Database schema</h2>
<p>The first thing we’ll need is a backend schema for passkeys. The only data we need to store for a particular passkey is a unique identifier, and the public key itself. We also want users to be able to have multiple passkeys associated with their account (since they may want to login from multiple platforms that don’t cross-sync passkeys), so the users schema will have a one-to-many relationship with the passkeys.</p>
<aside>
<p>This is one of those places I’m leaving things incomplete. What we’ll build will support having multiple passkeys tied to a single account, and let users log in with any of them. But I’m not going to implement the actual UI for adding additional ones—that’s left as an exercise for the reader ;)</p>
</aside>
<p>The actual model I’ll call <code>UserCredential</code>, since that’s closer to what the WebAuthn spec calls them. Here’s the schema, it’s pretty simple:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts.UserCredential</span> <span class="hl-kw">do</span>
  <span class="hl-kw">use</span> <span class="hl-mod">Ecto.Schema</span>
  <span class="hl-kw">import</span> <span class="hl-mod">Ecto.Changeset</span>

  <span class="hl-attr">@</span><span class="hl-attr">primary_key</span> {<span class="hl-str">:id</span>, <span class="hl-str">:binary</span>, []}

  <span class="hl-fn">schema</span> <span class="hl-str">"users_credentials"</span> <span class="hl-kw">do</span>
    <span class="hl-cmt"># DER-encoded Subject Public Key Info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.7</span>
    <span class="hl-fn">field</span> <span class="hl-str">:public_key_spki</span>, <span class="hl-str">:binary</span>
    <span class="hl-fn">belongs_to</span> <span class="hl-str">:user</span>, <span class="hl-mod">PhoenixPasskeys.Accounts.User</span>
    <span class="hl-fn">timestamps</span>()
  <span class="hl-kw">end</span>

  <span class="hl-kw">def</span> <span class="hl-fn">changeset</span>(credential, attrs) <span class="hl-kw">do</span>
    credential
    <span class="hl-op">|&gt;</span> <span class="hl-fn">cast</span>(attrs, [<span class="hl-str">:id</span>, <span class="hl-str">:public_key_spki</span>, <span class="hl-str">:user_id</span>])
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>There are a couple things to note here:</p>
<ol>
<li>First, we’re explicitly specifying that the primary key is a binary, since that’s what the WebAuthn API provides as the credential ID.</li>
<li>The <code>public_key_spki</code> field contains the data for the public key itself and the algorithm being used. We don’t care about the specific format, though, since we don’t have to parse it ourselves.</li>
</ol>
<p>To the generated <code>User</code> schema, I also added the <code>has_many</code> side of the relationship. There’s also a migration to create the credentials table. I won’t show it here, since it’s exactly what you’d expect—just make sure the <code>id</code> column is a binary.</p>
<h2>Registration JavaScript</h2>
<p>WebAuthn, being a modern web API, is a JavaScript API. This is a distinct disadvantage, if your users expect to be able to login from JavaScript-less browsers. Nonetheless, we proceed with the JS. Here’s the first bit:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-builtin">document</span>.<span class="hl-fn">addEventListener</span>(<span class="hl-str">"DOMContentLoaded"</span>, () <span class="hl-op">=&gt;</span> {
    <span class="hl-kw">const</span> registrationForm <span class="hl-op">=</span> <span class="hl-builtin">document</span>.<span class="hl-fn">getElementById</span>(<span class="hl-str">"registration-form"</span>);
    <span class="hl-kw">if</span> (registrationForm) {
        registrationForm.<span class="hl-fn">addEventListener</span>(<span class="hl-str">"submit"</span>, (event) <span class="hl-op">=&gt;</span> {
            event.<span class="hl-fn">preventDefault</span>();
            <span class="hl-fn">registerWebAuthnAccount</span>(registrationForm);
        });
    }
});
</code></pre>
<p>If we find the registration <code>&lt;form&gt;</code> element (note that I’ve added an ID (and also removed the password field)), we add a submit listener to it which prevents the form submission and instead calls the <code>registerWebAuthnAccount</code> function we’ll create next.</p>
<p>Before we get there, we’ll also write a brief helper function to check whether passkeys are actually available:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">supportsPasskeys</span>() {
    <span class="hl-kw">if</span> (<span class="hl-op">!</span><span class="hl-builtin">window</span>.<span class="hl-prop">PublicKeyCredential</span> <span class="hl-op">||</span> <span class="hl-op">!</span>PublicKeyCredential.<span class="hl-prop">isConditionalMediationAvailable</span>) {
		<span class="hl-kw">return</span> <span class="hl-const">false</span>;
	}
	<span class="hl-kw">const</span> [conditional, userVerifiying] <span class="hl-op">=</span> <span class="hl-kw">await</span> Promise.<span class="hl-fn">all</span>([
		PublicKeyCredential.<span class="hl-fn">isConditionalMediationAvailable</span>(),
		PublicKeyCredential.<span class="hl-fn">isUserVerifyingPlatformAuthenticatorAvailable</span>(),
	]);
	<span class="hl-kw">return</span> conditional <span class="hl-op">&amp;&amp;</span> userVerifiying;
}
</code></pre>
<p>The conditional mediation check establishes that WebAuthn conditional UI is available. This is what we’ll use for login, and is part of what makes using passkeys a nice, slick experience. Conditional UI will let us start a request for a passkey that doesn’t present anything until the user interacts with the browser’s passkey autofill UI.</p>
<p>The user-verifying platform authenticator check establishes that, well, there is a user-verifying platform authenticator. The platform authenticator part means an authenticator that’s part of the user’s device, not removable, and the user-verifying part means that the authenticator verifies the presence of the user (such as via biometrics).</p>
<p>In the <code>registerWebAuthnAccount</code> function, the first thing we need to do is check that both these conditions are met and passkeys are supported by the browser. If not, we’ll just bail out and registration won’t be possible.</p>
<aside>
<p>What you’d want to in an actual application depends on your circumstances. If you only want users to be able to register with passkeys, you should indicate that somehow, rather than failing silently as I’m doing here. Otherwise, you could fall back to an existing password-based registration flow.</p>
</aside>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-kw">if</span> (<span class="hl-op">!</span>(<span class="hl-kw">await</span> <span class="hl-fn">supportsPasskeys</span>())) {
        <span class="hl-kw">return</span>;
    }
}
</code></pre>
<p>Next, we’ll setup a big ol’ options object that we’ll pass to the WebAuthn API to specify what sort of credential we want. There’s going to be a whole lot of stuff here, so lets take it one piece at a time.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> createOptions <span class="hl-op">=</span> {
        <span class="hl-prop">publicKey</span>: {
			<span class="hl-prop">rp</span>: {
				<span class="hl-prop">id</span>: <span class="hl-str">"localhost"</span>,
				<span class="hl-prop">name</span>: <span class="hl-str">"Phoenix Passkeys"</span>,
			},
        },
    };
}
</code></pre>
<p>The RP is the Relying Party—that is, us, the party which relies on the credentials for authentication. The ID is the <a href="https://w3c.github.io/webauthn/#rp-id">domain of the RP</a>—just localhost for this example, though in reality you’d need to use your actual domain in production. The name is just a user-facing name for the RP.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> createOptions <span class="hl-op">=</span> {
        <span class="hl-prop">publicKey</span>: {
            <span class="hl-cmt">// ...</span>
            <span class="hl-prop">user</span>: {
                <span class="hl-prop">id</span>: <span class="hl-fn">generateUserID</span>(),
                <span class="hl-prop">name</span>: form[<span class="hl-str">"user[email]"</span>].<span class="hl-prop">value</span>,
                <span class="hl-prop">displayName</span>: <span class="hl-str">""</span>,
            },
        },
    };
}
</code></pre>
<p>Next up is some info about the user who the credential is for. The user’s “name” will just be the email that they entered in the form. The <code>displayName</code> value is required, per the spec, but we don’t have any other information so we just leave it blank and let the browser display the <code>name</code> only. The ID is where this gets a little weird, since we’re just generating a random 64-byte value:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">function</span> <span class="hl-fn">generateUserID</span>() {
	<span class="hl-kw">const</span> userID <span class="hl-op">=</span> <span class="hl-kw">new</span> Uint8Array(<span class="hl-num">64</span>);
	crypto.<span class="hl-fn">getRandomValues</span>(userID);
	<span class="hl-kw">return</span> userID;
}
</code></pre>
<p>If you were adding a passkey to an existing user account, you could use a server-specified value for the user ID and the browser would replace any existing credentials with the same user ID and RP ID. But, since the user is registering a new account at this point, we assume they don’t want to do that (and, moreover, we don’t have any ID we can use yet). The user ID will also be returned upon a successful login, which would let us look up the user that’s logging in. However, since we’re only going to allow a credential to belong to a single user, we can use the credential’s ID to uniquely determine the user.</p>
<p>Next up, we specify the types of public keys that we’ll accept. We’re going to support Ed25519, ES256, and RS256, since those are recommended by the WebAuthn spec:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> createOptions <span class="hl-op">=</span> {
        <span class="hl-prop">publicKey</span>: {
            <span class="hl-cmt">// ...</span>
            <span class="hl-prop">pubKeyCredParams</span>: [
                {<span class="hl-prop">type</span>: <span class="hl-str">"public-key"</span>, <span class="hl-prop">alg</span>: <span class="hl-op">-</span><span class="hl-num">8</span>}, <span class="hl-cmt">// Ed25519</span>
                {<span class="hl-prop">type</span>: <span class="hl-str">"public-key"</span>, <span class="hl-prop">alg</span>: <span class="hl-op">-</span><span class="hl-num">7</span>}, <span class="hl-cmt">// ES256</span>
                {<span class="hl-prop">type</span>: <span class="hl-str">"public-key"</span>, <span class="hl-prop">alg</span>: <span class="hl-op">-</span><span class="hl-num">257</span>}, <span class="hl-cmt">// RS256</span>
            ],
        },
    };
}
</code></pre>
<p>During the login process, the server will generate a challenge value that the client will sign and the server will verify was signed with the user’s private key. The spec also requires that we provide a challenge when creating a credential, but we’re not going to use it for anything (since the user is just now creating the credential, we have nothing trusted that we can verify the initial challenge against), so we just provide an empty value:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> createOptions <span class="hl-op">=</span> {
        <span class="hl-prop">publicKey</span>: {
            <span class="hl-cmt">// ...</span>
            <span class="hl-prop">challenge</span>: <span class="hl-kw">new</span> Uint8Array(),
        },
    };
}
</code></pre>
<p>Lastly, we give our requirements for authenticators that we want to use:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> createOptions <span class="hl-op">=</span> {
        <span class="hl-prop">publicKey</span>: {
            <span class="hl-cmt">// ...</span>
            <span class="hl-prop">authenticatorSelection</span>: {
                <span class="hl-prop">authenticatorAttachment</span>: <span class="hl-str">"platform"</span>,
                <span class="hl-prop">requireResidentKey</span>: <span class="hl-const">true</span>,
            },
        },
    };
}
</code></pre>
<p>We want only the platform authenticator, not anything else, like removable security keys. We also specify that we want a resident key. This usage of “resident” is deprecated terminology that’s enshrined in the API. Really it means we want a <em>discoverable</em> credential, so that the authenticator will surface it during the login process without us having to request the credential by ID. This is important to note, since it’s what prevents needing a separate username entry step.</p>
<p>Now that we (finally) have all the configuration options in place, we can actually proceed with the credential creation. We pass the options to <code>navigator.credentials.create</code> to actually create the WebAuthn credential. If that fails, we’ll just take the easy way out and alert to inform the user (in an actual service, you’d probably want better error handling).</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> credential <span class="hl-op">=</span> <span class="hl-kw">await</span> navigator.<span class="hl-prop">credentials</span>.<span class="hl-fn">create</span>(createOptions);
    <span class="hl-kw">if</span> (<span class="hl-op">!</span>credential) {
        <span class="hl-fn">alert</span>(<span class="hl-str">"Could not create credential"</span>);
        <span class="hl-kw">return</span>
    }
}
</code></pre>
<p>From the credential we get back, we need a few pieces of information. First is the decoded client data, which is an object that contains information about the credential creation request that occurred.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> clientDataJSON <span class="hl-op">=</span> <span class="hl-kw">new</span> TextDecoder().<span class="hl-fn">decode</span>(credential.<span class="hl-prop">response</span>.<span class="hl-prop">clientDataJSON</span>);
    <span class="hl-kw">const</span> clientData <span class="hl-op">=</span> <span class="hl-const">JSON</span>.<span class="hl-fn">parse</span>(clientDataJSON);
}
</code></pre>
<p>The <code>clientDataJSON</code> field of the response object contains the JSON-serialized object in an <code>ArrayBuffer</code>, so we decode that to text and then parse the JSON. With the decoded object, we do a consistency check with a couple pieces of data: the type of the request, whether or not it was cross-origin, and the actual origin being used.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">if</span> (clientData.<span class="hl-prop">type</span> <span class="hl-op">!==</span> <span class="hl-str">"webauthn.create"</span> <span class="hl-op">||</span> ((<span class="hl-str">"crossOrigin"</span> <span class="hl-kw">in</span> clientData) <span class="hl-op">&amp;&amp;</span> clientData.<span class="hl-prop">crossOrigin</span>) <span class="hl-op">||</span> clientData.<span class="hl-prop">origin</span> <span class="hl-op">!==</span> <span class="hl-str">"http://localhost:4000"</span>) {
        <span class="hl-fn">alert</span>(<span class="hl-str">"Invalid credential"</span>);
        <span class="hl-kw">return</span>;
    }
}
</code></pre>
<p>If it was not a creation request, the request was cross-origin, or the origin doesn’t match, we bail out. Note that in production, the origin should be checked against the actual production origin, not localhost. And again, in reality you’d want better error handling than just an alert.</p>
<p>Next, we need to get the authenticator data which is encoded in a binary format and pull a few pieces of data out of it. You can see the full format of the authenticator data <a href="https://w3c.github.io/webauthn/#sctn-authenticator-data">in the spec</a>, but the parts we’re interested in are the backed-up state and the credential ID, which is part of the <a href="https://w3c.github.io/webauthn/#attested-credential-data">attested credential data</a>.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> authenticatorData <span class="hl-op">=</span> <span class="hl-kw">new</span> Uint8Array(credential.<span class="hl-prop">response</span>.<span class="hl-fn">getAuthenticatorData</span>());
    <span class="hl-kw">const</span> backedUp <span class="hl-op">=</span> (authenticatorData[<span class="hl-num">32</span>] <span class="hl-op">&gt;&gt;</span> <span class="hl-num">4</span>) <span class="hl-op">&amp;</span> <span class="hl-num">1</span>;
    <span class="hl-kw">const</span> idLength <span class="hl-op">=</span> (authenticatorData[<span class="hl-num">53</span>] <span class="hl-op">&lt;&lt;</span> <span class="hl-num">8</span>) <span class="hl-op">|</span> authenticatorData[<span class="hl-num">54</span>];
    <span class="hl-kw">const</span> id <span class="hl-op">=</span> authenticatorData.<span class="hl-fn">slice</span>(<span class="hl-num">55</span>, <span class="hl-num">55</span> <span class="hl-op">+</span> idLength);
}
</code></pre>
<p>We get the backed-up bit from the flags byte at offset 32. Then we get the length of the credential ID, which is encoded as a big-endian, 16-bit integer at bytes 53 and 54. The ID itself immediately follows the length, thus starting at byte 55.</p>
<p>Before proceeding, we check that the <a href="https://w3c.github.io/webauthn/#sctn-credential-backup">backed-up bit</a> is set, indicating that the credential is backed-up and safe from the user losing the current device. If it’s not, we won’t let the user register with this passkey. I choose to do this since it’s recommended by Adam Langley’s blog post, but whether it’s actually necessary may depend on your specific circumstances.</p>
<aside>
<p>It seems like if we choose to block the user from registering with a passkey that’s already been generated, that passkey should be removed—since it won’t actually let them log in, and it may be confusing if it sticks around. But there doesn’t seem to be API to do that, so oh well.</p>
</aside>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">if</span> (backedUp <span class="hl-op">!==</span> <span class="hl-num">1</span>) {
        <span class="hl-fn">alert</span>(<span class="hl-str">"Can't register with non backed-up credential"</span>);
        <span class="hl-kw">return</span>;
    }
}
</code></pre>
<p>The last piece of data we need out of the credential is the actual public key:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> publicKey <span class="hl-op">=</span> credential.<span class="hl-prop">response</span>.<span class="hl-fn">getPublicKey</span>();
}
</code></pre>
<p>And with all that in place, we can actually initiate the registration. We’ll assemble a form data payload with all of the requisite values:</p>
<aside>
<details>
<summary>
There's also a simple helper function for converting the two ArrayBuffer values to base 64 (click me to expand).
</summary>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">function</span> <span class="hl-fn">arrayBufferToBase64</span>(buf) {
	<span class="hl-kw">let</span> binary <span class="hl-op">=</span> <span class="hl-str">""</span>;
	<span class="hl-kw">const</span> bytes <span class="hl-op">=</span> <span class="hl-kw">new</span> Uint8Array(buf);
	<span class="hl-kw">for</span> (<span class="hl-kw">let</span> i <span class="hl-op">=</span> <span class="hl-num">0</span>; i <span class="hl-op">&lt;</span> buf.<span class="hl-prop">byteLength</span>; i<span class="hl-op">++</span>) {
		binary <span class="hl-op">+=</span> String.<span class="hl-fn">fromCharCode</span>(bytes[i]);
	}
	<span class="hl-kw">return</span> <span class="hl-fn">btoa</span>(binary);
}
</code></pre></details>
</aside>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> body <span class="hl-op">=</span> <span class="hl-kw">new</span> FormData();
    body.<span class="hl-fn">append</span>(<span class="hl-str">"_csrf_token"</span>, form.<span class="hl-prop">_csrf_token</span>.<span class="hl-prop">value</span>);
    body.<span class="hl-fn">append</span>(<span class="hl-str">"email"</span>, form[<span class="hl-str">"user[email]"</span>].<span class="hl-prop">value</span>);
    body.<span class="hl-fn">append</span>(<span class="hl-str">"credential_id"</span>, <span class="hl-fn">arrayBufferToBase64</span>(id));
    body.<span class="hl-fn">append</span>(<span class="hl-str">"public_key_spki"</span>, <span class="hl-fn">arrayBufferToBase64</span>(publicKey));
}
</code></pre>
<p>We’ll use the body in a POST request to the registration endpoint. The response we get back will contain a status value to indicate whether the request was successful or not. If it was successful, the backend will have set the session cookie, and we can redirect to the home page and the new user will be logged in. If registration failed, we’ll alert the user.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(form) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> resp <span class="hl-op">=</span> <span class="hl-kw">await</span> <span class="hl-fn">fetch</span>(form.<span class="hl-prop">action</span>, {
        <span class="hl-prop">method</span>: <span class="hl-str">"POST"</span>,
        body,
    });
    <span class="hl-kw">const</span> respJSON <span class="hl-op">=</span> <span class="hl-kw">await</span> resp.<span class="hl-fn">json</span>();
    <span class="hl-kw">if</span> (respJSON.<span class="hl-prop">status</span> <span class="hl-op">===</span> <span class="hl-str">"ok"</span>) {
        <span class="hl-builtin">window</span>.<span class="hl-prop">location</span> <span class="hl-op">=</span> <span class="hl-str">"/"</span>;
    } <span class="hl-kw">else</span> {
        <span class="hl-fn">alert</span>(<span class="hl-str">"Registration failed"</span>);
    }
}
</code></pre>
<p>And with that, we’re done with JavaScript (for now) and can move on to the backend part of registering an account.</p>
<h2>Creating a user</h2>
<p>In the <code>UserRegistrationController</code> module that comes with the Phoenix auth template, we’ll change the <code>create</code> function. By default, it registers the user using the parameters from the signup form and then redirects to the homepage. Instead, we’re going to register using the passkey we created on the client and then respond with the JSON that our JavaScript is expecting.</p>
<p>The first thing we need to do is extract the values that were sent by the frontend and decode the base 64-encoded ones.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserRegistrationController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">create</span>(conn, %{
        <span class="hl-str">"email"</span> <span class="hl-op">=&gt;</span> email,
        <span class="hl-str">"credential_id"</span> <span class="hl-op">=&gt;</span> credential_id,
        <span class="hl-str">"public_key_spki"</span> <span class="hl-op">=&gt;</span> public_key_spki
      }) <span class="hl-kw">do</span>
    credential_id <span class="hl-op">=</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>(credential_id)
    public_key_spki <span class="hl-op">=</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>(public_key_spki)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Those get passed to the <code>Accounts.register_user</code> function, which we’ll update shortly to handle. If account creation succeeded, we’ll still send the confirmation email as the existing code did. After that, instead of redirecting, we’ll log the user in by setting the session cookie and then respond with the “ok” status for the frontend. If account creation fails, we’ll just respond with the “error” status so the frontend can alert the user.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserRegistrationController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">create</span>(...) <span class="hl-kw">do</span>
    <span class="hl-cmt"># ...</span>
    <span class="hl-kw">case</span> <span class="hl-mod">Accounts</span><span class="hl-op">.</span><span class="hl-fn">register_user</span>(email, credential_id, public_key_spki) <span class="hl-kw">do</span>
      {<span class="hl-str">:ok</span>, user} <span class="hl-op">-&gt;</span>
        <span class="hl-cmt"># Send confirmation email...</span>

        conn
        <span class="hl-op">|&gt;</span> <span class="hl-mod">UserAuth</span><span class="hl-op">.</span><span class="hl-fn">log_in_user_without_redirect</span>(user)
        <span class="hl-op">|&gt;</span> <span class="hl-fn">json</span>(%{<span class="hl-str">status: </span><span class="hl-str">:ok</span>})

      {<span class="hl-str">:error</span>, <span class="hl-cmt">_changeset</span>} <span class="hl-op">-&gt;</span>
        <span class="hl-fn">json</span>(conn, %{<span class="hl-str">status: </span><span class="hl-str">:error</span>})
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Let’s update the <code>register_user</code> function. Instead of just creating a changeset for the user and then inserting it, we need to also create the authenticator. To avoid potentially leaving things in a broken state, we wrap both of these in a database transaction.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">register_user</span>(email, credential_id, public_key_spki) <span class="hl-kw">do</span>
    <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">transaction</span>(<span class="hl-kw">fn</span> <span class="hl-op">-&gt;</span>
      user <span class="hl-op">=</span>
        %<span class="hl-mod">User</span>{}
        <span class="hl-op">|&gt;</span> <span class="hl-mod">User</span><span class="hl-op">.</span><span class="hl-fn">registration_changeset</span>(%{<span class="hl-str">email: </span>email})
        <span class="hl-op">|&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">insert</span>()
        <span class="hl-op">|&gt;</span> <span class="hl-kw">case</span> <span class="hl-kw">do</span>
          {<span class="hl-str">:ok</span>, user} <span class="hl-op">-&gt;</span> user
          {<span class="hl-str">:error</span>, changeset} <span class="hl-op">-&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">rollback</span>(changeset)
        <span class="hl-kw">end</span>
    <span class="hl-kw">end</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>First, we create a user with the given email. If the user creation fails, we abort and rollback the transaction. Then, we can create a credential belonging to the new user with the credential ID and public key we received from the client. As with the user, if creating the credential fails, we rollback the transaction.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">register_user</span>(email, credential_id, public_key_spki) <span class="hl-kw">do</span>
    <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">transaction</span>(<span class="hl-kw">fn</span> <span class="hl-op">-&gt;</span>
      <span class="hl-cmt"># ...</span>
      %<span class="hl-mod">UserCredential</span>{}
      <span class="hl-op">|&gt;</span> <span class="hl-mod">UserCredential</span><span class="hl-op">.</span><span class="hl-fn">changeset</span>(%{
        <span class="hl-str">id: </span>credential_id,
        <span class="hl-str">public_key_spki: </span>public_key_spki,
        <span class="hl-str">user_id: </span>user<span class="hl-op">.</span><span class="hl-prop">id</span>
      })
      <span class="hl-op">|&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">insert</span>()
      <span class="hl-op">|&gt;</span> <span class="hl-kw">case</span> <span class="hl-kw">do</span>
        {<span class="hl-str">:ok</span>, <span class="hl-cmt">_credential</span>} <span class="hl-op">-&gt;</span> <span class="hl-const">nil</span>
        {<span class="hl-str">:error</span>, changeset} <span class="hl-op">-&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">rollback</span>(changeset)
      <span class="hl-kw">end</span>
    <span class="hl-kw">end</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>We don’t need to do anything with the newly created credential, so we can just ignore it once it’s been created.</p>
<p>And finally, from the transaction function, we return the user:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">register_user</span>(email, credential_id, public_key_spki) <span class="hl-kw">do</span>
    <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">transaction</span>(<span class="hl-kw">fn</span> <span class="hl-op">-&gt;</span>
      <span class="hl-cmt"># ...</span>
      user
    <span class="hl-kw">end</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>The last thing we need to to complete the registration flow is to set the new user’s session cookie so that they’re logged in immediately. The <code>UserAuth</code> module that’s generated as part of the Phoenix auth template has a <code>log_in_user</code> function that does exactly this. But it also redirects the connection to another endpoint. We don’t want to do that, since we’re sending a JSON response, so I’ve split the function into two: one that only sets the session, and the existing function that sets the session and then redirects.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserAuth</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">log_in_user</span>(conn, user, params <span class="hl-op">\\</span> %{}) <span class="hl-kw">do</span>
    user_return_to <span class="hl-op">=</span> <span class="hl-fn">get_session</span>(conn, <span class="hl-str">:user_return_to</span>)
    conn
    <span class="hl-op">|&gt;</span> <span class="hl-fn">log_in_user_without_redirect</span>(user, params)
    <span class="hl-op">|&gt;</span> <span class="hl-fn">redirect</span>(<span class="hl-str">to: </span>user_return_to <span class="hl-op">||</span> <span class="hl-fn">signed_in_path</span>(conn))
  <span class="hl-kw">end</span>

  <span class="hl-kw">def</span> <span class="hl-fn">log_in_user_without_redirect</span>(conn, user, params <span class="hl-op">\\</span> %{}) <span class="hl-kw">do</span>
    token <span class="hl-op">=</span> <span class="hl-mod">Accounts</span><span class="hl-op">.</span><span class="hl-fn">generate_user_session_token</span>(user)
    conn
    <span class="hl-op">|&gt;</span> <span class="hl-fn">renew_session</span>()
    <span class="hl-op">|&gt;</span> <span class="hl-fn">put_token_in_session</span>(token)
    <span class="hl-op">|&gt;</span> <span class="hl-fn">maybe_write_remember_me_cookie</span>(token, params)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>And with that, everything is in place and you can now create an account with a passkey!</p>
<h2>Login form</h2>
<p>Now that the user’s got an account, they need to be able to login with it. That means once again interacting with the WebAuthn API and writing a bunch of JavaScript. But before we get there, we need some slight changes to the backend.</p>
<p>In the HTML for the login page, we’ll add an ID to the <code>&lt;form&gt;</code> element so that we can find it from JS. We’ll also remove the password field, which is obviously no longer necessary. Lastly, but certainly not least, we need to send a challenge to the client.</p>
<p>The challenge is a value that the user’s device will cryptographically sign with their private key. The result will get sent back to the server, and we’ll verify it against the public key we have stored thus authenticating them. We’ll send the challenge just in a hidden form field:</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">input</span> <span class="hl-attr">type</span>="<span class="hl-str">hidden</span>" <span class="hl-attr">id</span>="<span class="hl-str">challenge</span>" <span class="hl-attr">value</span>=<span class="hl-str">{@webauthn_challenge}</span> /&gt;
</code></pre>
<p>In the session controller, we’ll need to generate and assign the challenge to the connection.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">new</span>(conn, <span class="hl-cmt">_params</span>) <span class="hl-kw">do</span>
    conn
    <span class="hl-op">|&gt;</span> <span class="hl-fn">put_webauthn_challenge</span>()
    <span class="hl-op">|&gt;</span> <span class="hl-fn">render</span>(<span class="hl-str">:new</span>, <span class="hl-str">error_message: </span><span class="hl-const">nil</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>WebAuthn expects the challenge to be a value up to 64 bytes long, so we’ll use the Erlang crypto module to generate one of that length. The value is encoded as <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">URL-safe base 64</a> (the same as normal base 64, but with dash and underscore rather than plus and slash) without padding. We encode it this way since that’s the format in which it will later be <a href="https://w3c.github.io/webauthn/#dom-collectedclientdata-challenge">returned</a> as part of the <code>clientDataJSON</code>, so when we extract that value we can directly compare it to the challenge value we generated.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">defp</span> <span class="hl-fn">put_webauthn_challenge</span>(conn) <span class="hl-kw">do</span>
    challenge <span class="hl-op">=</span>
      <span class="hl-mod">:crypto</span><span class="hl-op">.</span><span class="hl-fn">strong_rand_bytes</span>(<span class="hl-num">64</span>)
      <span class="hl-op">|&gt;</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">url_encode64</span>(<span class="hl-str">padding: </span><span class="hl-const">false</span>)

    conn
    <span class="hl-op">|&gt;</span> <span class="hl-fn">put_session</span>(<span class="hl-str">:webauthn_challenge</span>, challenge)
    <span class="hl-op">|&gt;</span> <span class="hl-fn">assign</span>(<span class="hl-str">:webauthn_challenge</span>, challenge)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Note that the challenge string is also stored in the session, so that we can later check that the challenge that the client signed matches the challenge we generated. It’s safe to store this in the session, even though it’s sent to the client, because the cookie is encrypted and signed so the client can’t tamper with it.</p>
<h2>Login JavaScript</h2>
<p>With the first part of the backend changes taken care of, it’s time for more JavaScript, baby!</p>
<p>We’ll follow a similar outline to the registration setup (and the same caveat applies about error handling).</p>
<pre class="highlight" data-lang="js"><code><span class="hl-builtin">document</span>.<span class="hl-fn">addEventListener</span>(<span class="hl-str">"DOMContentLoaded"</span>, () <span class="hl-op">=&gt;</span> {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> loginForm <span class="hl-op">=</span> <span class="hl-builtin">document</span>.<span class="hl-fn">getElementById</span>(<span class="hl-str">"login-form"</span>);
	<span class="hl-kw">if</span> (loginForm) {
        <span class="hl-fn">loginWebAuthnAccount</span>(loginForm);
    }
});

<span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">loginWebAuthnAccount</span>(loginForm) {
    <span class="hl-kw">if</span> (<span class="hl-op">!</span>(<span class="hl-kw">await</span> <span class="hl-fn">supportsPasskeys</span>())) {
        <span class="hl-kw">return</span>;
    }
}
</code></pre>
<p>The first thing we need to do is grab the challenge from the hidden form field, then we can construct the options object for getting the credential (which is, thankfully, much simpler than for creation).</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">loginWebAuthnAccount</span>(loginForm) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> challenge <span class="hl-op">=</span> loginForm.<span class="hl-prop">challenge</span>.<span class="hl-prop">value</span>;
    <span class="hl-kw">const</span> getOptions <span class="hl-op">=</span> {
        <span class="hl-prop">mediation</span>: <span class="hl-str">"conditional"</span>,
        <span class="hl-prop">publicKey</span>: {
            <span class="hl-prop">challenge</span>: <span class="hl-fn">base64URLToArrayBuffer</span>(challenge),
            <span class="hl-prop">rpId</span>: <span class="hl-str">"localhost"</span>,
        },
    };
}
</code></pre><aside>
<details>
<summary>
Here are the little helper functions that handle decoding from both regular and URL-safe base 64.
</summary>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">function</span> <span class="hl-fn">base64URLToArrayBuffer</span>(b64) {
	<span class="hl-kw">const</span> converted <span class="hl-op">=</span> b64.<span class="hl-fn">replace</span>(<span class="hl-str"><span class="hl-op">/</span>[-_]<span class="hl-op">/</span>g</span>, (c) <span class="hl-op">=&gt;</span> c <span class="hl-op">===</span> <span class="hl-str">"-"</span> ? <span class="hl-str">"+"</span> : <span class="hl-str">"/"</span>);
	<span class="hl-kw">return</span> <span class="hl-fn">base64ToArrayBuffer</span>(converted);
}
<span class="hl-kw">function</span> <span class="hl-fn">base64ToArrayBuffer</span>(b64) {
	<span class="hl-kw">const</span> bin <span class="hl-op">=</span> <span class="hl-fn">atob</span>(b64);
	<span class="hl-kw">const</span> bytes <span class="hl-op">=</span> <span class="hl-kw">new</span> Uint8Array(bin.<span class="hl-prop">length</span>);
	<span class="hl-kw">for</span> (<span class="hl-kw">let</span> i <span class="hl-op">=</span> <span class="hl-num">0</span>; i <span class="hl-op">&lt;</span> bin.<span class="hl-prop">length</span>; i<span class="hl-op">++</span>) {
		bytes[i] <span class="hl-op">=</span> bin.<span class="hl-fn">charCodeAt</span>(i);
	}
	<span class="hl-kw">return</span> bytes.<span class="hl-prop">buffer</span>;
}
</code></pre></details>
</aside>
<p>In the options, we specify that we want conditional mediation. As noted before, this means that the browser won’t display any UI, except for autofill, for this credential request until the user accepts the autofill suggestion. In the public key options, we also give the decoded challenge value and specify the our Relying Party ID (again, this would need to be the actual domain in production).</p>
<p>Now, we can actually make the credential request and then, if we get a credential back, encode and send all the values to the backend. We need to send the ID of the credential, so that the backend can find its public key and the corresponding user. We also need the client data JSON, which we send as text decoded from the <code>ArrayBuffer</code> it’s returned as. We also need to send the authenticator data as well as the signature itself.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">loginWebAuthnAccount</span>(loginForm) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> credential <span class="hl-op">=</span> <span class="hl-kw">await</span> navigator.<span class="hl-prop">credentials</span>.<span class="hl-fn">get</span>(getOptions);
    <span class="hl-kw">if</span> (<span class="hl-op">!</span>credential) {
        <span class="hl-fn">alert</span>(<span class="hl-str">"Could not get credential"</span>);
        <span class="hl-kw">return</span>;
    }

    <span class="hl-kw">const</span> clientDataJSON <span class="hl-op">=</span> <span class="hl-kw">new</span> TextDecoder().<span class="hl-fn">decode</span>(credential.<span class="hl-prop">response</span>.<span class="hl-prop">clientDataJSON</span>);

    <span class="hl-kw">const</span> body <span class="hl-op">=</span> <span class="hl-kw">new</span> FormData();
    body.<span class="hl-fn">append</span>(<span class="hl-str">"_csrf_token"</span>, loginForm.<span class="hl-prop">_csrf_token</span>.<span class="hl-prop">value</span>);
    body.<span class="hl-fn">append</span>(<span class="hl-str">"raw_id"</span>, <span class="hl-fn">arrayBufferToBase64</span>(credential.<span class="hl-prop">rawId</span>));
    body.<span class="hl-fn">append</span>(<span class="hl-str">"client_data_json"</span>, clientDataJSON);
    body.<span class="hl-fn">append</span>(<span class="hl-str">"authenticator_data"</span>, <span class="hl-fn">arrayBufferToBase64</span>(credential.<span class="hl-prop">response</span>.<span class="hl-prop">authenticatorData</span>));
    body.<span class="hl-fn">append</span>(<span class="hl-str">"signature"</span>, <span class="hl-fn">arrayBufferToBase64</span>(credential.<span class="hl-prop">response</span>.<span class="hl-prop">signature</span>));
}
</code></pre>
<p>We can then send a request to the login endpoint. If the backend request fails (or if we failed to get the credential) we just alert the user (again, you’d probably want something better in reality). If the login attempt was successful, the server will have set the session cookie, and so we can just redirect to the homepage and the user will be logged in.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">loginWebAuthnAccount</span>(loginForm) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> resp <span class="hl-op">=</span> <span class="hl-kw">await</span> <span class="hl-fn">fetch</span>(<span class="hl-str">"/users/log_in"</span>, {
        <span class="hl-prop">method</span>: <span class="hl-str">"POST"</span>,
        body,
    });
    <span class="hl-kw">const</span> respJSON <span class="hl-op">=</span> <span class="hl-kw">await</span> resp.<span class="hl-fn">json</span>();
    <span class="hl-kw">if</span> (respJSON?.<span class="hl-prop">status</span> <span class="hl-op">===</span> <span class="hl-str">"ok"</span>) {
        <span class="hl-builtin">window</span>.<span class="hl-prop">location</span> <span class="hl-op">=</span> <span class="hl-str">"/"</span>;
    } <span class="hl-kw">else</span> {
        <span class="hl-fn">alert</span>(<span class="hl-str">"Login failed"</span>);
    }
}
</code></pre>
<p>With that in place, let’s move on to the backend half of the login request.</p>
<h2>Validating a login attempt</h2>
<p>As with signup, we’ll modify the existing log in endpoint to actually validate the WebAuthn login attempt.</p>
<p>The first step is extracting all of the information the frontend provides in the params and decoding it:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">create</span>(conn, params) <span class="hl-kw">do</span>
    id <span class="hl-op">=</span> params <span class="hl-op">|&gt;</span> <span class="hl-mod">Map</span><span class="hl-op">.</span><span class="hl-fn">get</span>(<span class="hl-str">"raw_id"</span>) <span class="hl-op">|&gt;</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>()
    authenticator_data <span class="hl-op">=</span> params <span class="hl-op">|&gt;</span> <span class="hl-mod">Map</span><span class="hl-op">.</span><span class="hl-fn">get</span>(<span class="hl-str">"authenticator_data"</span>) <span class="hl-op">|&gt;</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>()
    client_data_json_str <span class="hl-op">=</span> params <span class="hl-op">|&gt;</span> <span class="hl-mod">Map</span><span class="hl-op">.</span><span class="hl-fn">get</span>(<span class="hl-str">"client_data_json"</span>)
    signature <span class="hl-op">=</span> params <span class="hl-op">|&gt;</span> <span class="hl-mod">Map</span><span class="hl-op">.</span><span class="hl-fn">get</span>(<span class="hl-str">"signature"</span>) <span class="hl-op">|&gt;</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>()
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Next, we’re going to validate all of the information we got from the client. Before we get to that, there are a handful of helper functions we’ll use. First, looking up a credential by its ID:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">get_credential</span>(id) <span class="hl-kw">do</span>
    <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">get</span>(<span class="hl-mod">UserCredential</span>, id)
    <span class="hl-op">|&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">preload</span>(<span class="hl-str">:user</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Next, a function in the controller that verifies the signature against the provided data:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">defp</span> <span class="hl-fn">verify_signature</span>(credential, client_data_json_str, authenticator_data, signature) <span class="hl-kw">do</span>
    <span class="hl-kw">with</span> {<span class="hl-str">:ok</span>, pubkey} <span class="hl-op">&lt;-</span> <span class="hl-mod">X509.PublicKey</span><span class="hl-op">.</span><span class="hl-fn">from_der</span>(credential<span class="hl-op">.</span><span class="hl-prop">public_key_spki</span>),
         client_data_json_hash <span class="hl-op">&lt;-</span> <span class="hl-mod">:crypto</span><span class="hl-op">.</span><span class="hl-fn">hash</span>(<span class="hl-str">:sha256</span>, client_data_json_str),
         signed_message <span class="hl-op">&lt;-</span> authenticator_data <span class="hl-op">&lt;&gt;</span> client_data_json_hash,
         <span class="hl-const">true</span> <span class="hl-op">&lt;-</span> <span class="hl-mod">:public_key</span><span class="hl-op">.</span><span class="hl-fn">verify</span>(signed_message, <span class="hl-str">:sha256</span>, signature, pubkey) <span class="hl-kw">do</span>
      <span class="hl-const">true</span>
    <span class="hl-kw">else</span>
      <span class="hl-cmt">_</span> <span class="hl-op">-&gt;</span>
        <span class="hl-const">false</span>
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p><code>X509</code> comes from the <a href="https://hex.pm/packages/x509"><code>x509</code></a> package, which is the only third-party piece of code we’re using. It’s a fairly thin wrapper around the Erlang <code>public_key</code> and <code>crypto</code> modules, and mostly serves to save me from having to deal with Erlang records in my code. Its <code>from_der</code> helper function is used to parse the public key from the encoded format.</p>
<p>Next, we hash the client data JSON and append that hash to the authenticator data from the client. This value is what should match the signature using the public key we’ve got, so finally we check that. If all these steps succeeded, we return true, and false otherwise.</p>
<p>The last helper function will receive the decoded client data make sure it’s got all of the values that we expect. If the <code>crossOrigin</code> value is present and is not false, the client data is invalid and the login attempt will be rejected.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">defp</span> <span class="hl-fn">check_client_data_json</span>(%{<span class="hl-str">"crossOrigin"</span> <span class="hl-op">=&gt;</span> crossOrigin}) <span class="hl-kw">when</span> crossOrigin <span class="hl-op">!=</span> <span class="hl-const">false</span> <span class="hl-kw">do</span>
    <span class="hl-const">false</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Otherwise, we check that the data has the expected type and origin, and we extract the challenge value (note that we’re checking the origin again here, and this would need to change in production):</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">defp</span> <span class="hl-fn">check_client_data_json</span>(%{
         <span class="hl-str">"type"</span> <span class="hl-op">=&gt;</span> <span class="hl-str">"webauthn.get"</span>,
         <span class="hl-str">"challenge"</span> <span class="hl-op">=&gt;</span> challenge,
         <span class="hl-str">"origin"</span> <span class="hl-op">=&gt;</span> <span class="hl-str">"http://localhost:4000"</span>
       }) <span class="hl-kw">do</span>
    {<span class="hl-str">:ok</span>, challenge}
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>And lastly, if neither of the previous patterns matched, the client data fails validation:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">defp</span> <span class="hl-fn">check_client_data_json</span>(<span class="hl-cmt">_</span>), <span class="hl-str">do: </span><span class="hl-const">false</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Now, let’s put all those parts together and validate the login attempt.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">create</span>(conn, params) <span class="hl-kw">do</span>
    <span class="hl-cmt"># ...</span>
    <span class="hl-kw">with</span> credential <span class="hl-kw">when</span> <span class="hl-kw">not</span> <span class="hl-fn">is_nil</span>(credential) <span class="hl-op">&lt;-</span> <span class="hl-mod">Accounts</span><span class="hl-op">.</span><span class="hl-fn">get_credential</span>(id),
         <span class="hl-const">true</span> <span class="hl-op">&lt;-</span> <span class="hl-fn">verify_signature</span>(credential, client_data_json_str, authenticator_data, signature),
         {<span class="hl-str">:ok</span>, client_data_json} <span class="hl-op">&lt;-</span> <span class="hl-mod">Jason</span><span class="hl-op">.</span><span class="hl-fn">decode</span>(client_data_json_str),
         {<span class="hl-str">:ok</span>, challenge} <span class="hl-op">&lt;-</span> <span class="hl-fn">check_client_data_json</span>(client_data_json),
         <span class="hl-const">true</span> <span class="hl-op">&lt;-</span> challenge <span class="hl-op">==</span> <span class="hl-fn">get_session</span>(conn, <span class="hl-str">:webauthn_challenge</span>),
         <span class="hl-const">true</span> <span class="hl-op">&lt;-</span> <span class="hl-mod">:binary</span><span class="hl-op">.</span><span class="hl-fn">part</span>(authenticator_data, <span class="hl-num">0</span>, <span class="hl-num">32</span>) <span class="hl-op">==</span> <span class="hl-mod">:crypto</span><span class="hl-op">.</span><span class="hl-fn">hash</span>(<span class="hl-str">:sha256</span>, <span class="hl-str">"localhost"</span>),
         <span class="hl-const">true</span> <span class="hl-op">&lt;-</span> (<span class="hl-mod">:binary</span><span class="hl-op">.</span><span class="hl-fn">at</span>(authenticator_data, <span class="hl-num">32</span>) <span class="hl-op">&amp;&amp;&amp;</span> <span class="hl-num">1</span>) <span class="hl-op">==</span> <span class="hl-num">1</span> <span class="hl-kw">do</span>
      conn
      <span class="hl-op">|&gt;</span> <span class="hl-fn">delete_session</span>(<span class="hl-str">:webauthn_challenge</span>)
      <span class="hl-op">|&gt;</span> <span class="hl-mod">UserAuth</span><span class="hl-op">.</span><span class="hl-fn">log_in_user_without_redirect</span>(credential<span class="hl-op">.</span><span class="hl-prop">user</span>)
      <span class="hl-op">|&gt;</span> <span class="hl-fn">json</span>(%{<span class="hl-str">status: </span><span class="hl-str">:ok</span>})
    <span class="hl-kw">else</span>
      <span class="hl-cmt">_</span> <span class="hl-op">-&gt;</span>
        <span class="hl-fn">json</span>(conn, %{<span class="hl-str">status: </span><span class="hl-str">:error</span>})
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Here’s everything that we’re doing:</p>
<ol>
<li>Lookup the credential with the given ID.</li>
<li>Use the public key we had stored to verify the signature on the authenticator data and client data JSON.</li>
<li>Decode the client data JSON.</li>
<li>Check that all the values in the client data are what we expect, and extract the challenge that was signed.</li>
<li>Ensure the challenge that the user signed matches what we previously generated.</li>
<li>Extract the hash of the origin from the <a href="https://w3c.github.io/webauthn/#authenticator-data">authenticator data</a> and ensure it matches our origin (this would not be localhost in production).</li>
<li>Check that the authenticator data has the user presence bit set (indicating that a person was actually present on the client’s end).</li>
</ol>
<p>If all of those steps succeeded, we remove the old challenge value from the session (since it’s no longer needed), actually log the user in, and then respond with the “ok” status that the JavaScript is expecting. If any step failed, we’ll respond with the “error” status and the frontend will alert the user.</p>
<p>Since there’s a lot going on here, it’s worth being clear about what exactly in this process lets us authenticate and prove that the user is who they claim to be. Since the signature verification step is using the public key that we stored during the registration process, we know that anyone that can produce a valid signature using that public key must be the user (or at any rate, have their private key). The value that they’re signing is, essentially, the challenge: a securely generated random value. The user isn’t directly signing the challenge, but this is still safe, since the challenge value is included in the client data JSON, that hash of which is included in the signed message.</p>
<p>So: the challenge value that was signed by the user must be in the client data, and the challenge value in the client data must be the one we generated. Given that, we know that the user whose key was used to sign the message is the one trying to log in now. That we’re verifying with the stored public key prevents an attacker from using an arbitrary key to sign the login attempt. And that the signed challenge matches the challenge the server generated means an attacker can’t reuse a previous response to login (a replay attack).</p>
<p>At long last, we finally have the ability to log in to our application using a passkey. Only a few minor things to go, so let’s forge ahead.</p>
<h2>Handling login if the user enters an email</h2>
<p>Although we’re presenting the conditional UI, there’s nothing preventing the user from typing their email into the field and then clicking “Sign in,” so we should probably handle that to. This can be done fairly simply by reusing our existing code for conditional login.</p>
<p>We’ll change the <code>loginWebAuthnAccount</code> to take an additional parameter, <code>conditional</code>, which will be a boolean indicating whether this login attempt is to setup the conditional UI or triggered by submitting the login form.</p>
<p>If it’s false, we won’t request conditional mediation and instead we’ll look up the credentials corresponding to the email the user entered and ask WebAuthn for one of those:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">loginWebAuthnAccount</span>(loginForm, conditional) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">let</span> allowCredentials <span class="hl-op">=</span> [];
    <span class="hl-kw">if</span> (<span class="hl-op">!</span>conditional) {
		<span class="hl-kw">const</span> email <span class="hl-op">=</span> loginForm[<span class="hl-str">"user[email]"</span>].<span class="hl-prop">value</span>;
		<span class="hl-kw">const</span> resp <span class="hl-op">=</span> <span class="hl-kw">await</span> <span class="hl-fn">fetch</span>(<span class="hl-str">`/users/log_in/credentials?email=<span class="hl-rst"><span class="hl-psp">${</span>email<span class="hl-psp">}</span></span>`</span>);
		<span class="hl-kw">const</span> respJSON <span class="hl-op">=</span> <span class="hl-kw">await</span> resp.<span class="hl-fn">json</span>();
		allowCredentials <span class="hl-op">=</span> respJSON.<span class="hl-fn">map</span>((id) <span class="hl-op">=&gt;</span> {
			<span class="hl-kw">return</span> {
				<span class="hl-prop">type</span>: <span class="hl-str">"public-key"</span>,
				<span class="hl-prop">id</span>: <span class="hl-fn">base64ToArrayBuffer</span>(id),
			};
		});
    }

    <span class="hl-kw">const</span> getOptions <span class="hl-op">=</span> {
		<span class="hl-prop">mediation</span>: conditional ? <span class="hl-str">"conditional"</span> : <span class="hl-str">"optional"</span>,
		<span class="hl-prop">publicKey</span>: {
			<span class="hl-prop">challenge</span>: <span class="hl-fn">base64URLToArrayBuffer</span>(challenge),
			<span class="hl-prop">rpId</span>: <span class="hl-str">"localhost"</span>,
			allowCredentials,
		}
    };
    <span class="hl-cmt">// ...</span>
}
</code></pre>
<p>The “optional” value for the <code>mediation</code> option means that the authenticator isn’t required to display UI, but will do so if its policies dictate that. The <code>allowCredentials</code> array contains objects describing all of the credentials that we want to accept—specifically, their binary IDs as <code>ArrayBuffer</code>s.</p>
<p>We look up the user’s credentials so that the one we actually request from the authenticator matches the account that the user is trying to log in with. To handle this, we’ll also wire up an additional route on the backend that returns the base 64-encoded IDs of all the credentials belonging to the user with a given email.</p>
<aside>
<p>This is a vector for user enumeration, which may or may not matter depending on your particular use case. If it does matter and you can’t do this, I would forego conditional UI entirely have the WebAuthn flow triggered by a plain button press or something.</p>
</aside>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">credentials</span>(conn, %{<span class="hl-str">"email"</span> <span class="hl-op">=&gt;</span> email}) <span class="hl-kw">do</span>
    ids <span class="hl-op">=</span>
      <span class="hl-mod">Accounts</span><span class="hl-op">.</span><span class="hl-fn">get_credentials_by_email</span>(email)
      <span class="hl-op">|&gt;</span> <span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">map</span>(<span class="hl-kw">fn</span> cred <span class="hl-op">-&gt;</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">encode64</span>(cred<span class="hl-op">.</span><span class="hl-prop">id</span>) <span class="hl-kw">end</span>)

    <span class="hl-fn">json</span>(conn, ids)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>The <code>get_credentials_by_email</code> function is quite simple. It just looks up a user by email, preloading any credentials they have and then returning them:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">get_credentials_by_email</span>(email) <span class="hl-kw">do</span>
    <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">one</span>(<span class="hl-fn">from</span> u <span class="hl-kw">in</span> <span class="hl-mod">User</span>, <span class="hl-str">where: </span>u<span class="hl-op">.</span><span class="hl-prop">email</span> <span class="hl-op">==</span> <span class="hl-op">^</span>email, <span class="hl-str">preload: </span><span class="hl-str">:credentials</span>)
    <span class="hl-op">|&gt;</span> <span class="hl-kw">case</span> <span class="hl-kw">do</span>
      %{<span class="hl-str">credentials: </span>credentials} <span class="hl-op">-&gt;</span> credentials
      <span class="hl-const">nil</span> <span class="hl-op">-&gt;</span> []
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Back in the JS, we can tweak the setup code to pass <code>true</code> for the conditional parameter in the initial request and also register a submit handler on the login form that will invoke it with <code>false</code>:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-builtin">document</span>.<span class="hl-fn">addEventListener</span>(<span class="hl-str">"DOMContentLoaded"</span>, () <span class="hl-op">=&gt;</span> {
    <span class="hl-cmt">// ...</span>
	<span class="hl-kw">const</span> loginForm <span class="hl-op">=</span> <span class="hl-builtin">document</span>.<span class="hl-fn">getElementById</span>(<span class="hl-str">"login-form"</span>);
	<span class="hl-kw">if</span> (loginForm) {
		<span class="hl-fn">loginWebAuthnAccount</span>(loginForm, <span class="hl-const">true</span>);
		loginForm.<span class="hl-fn">addEventListener</span>(<span class="hl-str">"submit"</span>, (event) <span class="hl-op">=&gt;</span> {
			event.<span class="hl-fn">preventDefault</span>();
			<span class="hl-fn">loginWebAuthnAccount</span>(loginForm, <span class="hl-const">false</span>);
		});
	}
});
</code></pre>
<p>And so we’ve handled the case where the user ignores the conditional UI and still types in their email to log in.</p>
<h2>Passkey reset</h2>
<p>A not-infrequent argument against passkeys is that if your phone falls into a lake, you lose access to all of your passkey-backed accounts. One could argue that this isn’t true because the definition of passkeys means that they’re backed up and thus protected from this sort of event (and indeed, earlier we only permitted registering with backed-up credentials). I think the argument isn’t particularly interesting, however, because you can still have a “Forgot my passkey” option that works just like it does now with passwords.</p>
<p>This is less secure than a passkey implementation that has no “Forgot” option. But it’s no less secure than current password-based systems, and I think the UX/security tradeoff here falls on the UX side—people will, inevitably, lose access to their passkeys while retaining access to their email.</p>
<p>Implementing isn’t too complicated, fortunately, since we can reuse much of the registration code. First, the JavaScript. The only change necessary is attaching the registration function to the reset form as well.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-builtin">document</span>.<span class="hl-fn">addEventListener</span>(<span class="hl-str">"DOMContentLoaded"</span>, () <span class="hl-op">=&gt;</span> {
    <span class="hl-cmt">// ...</span>
	<span class="hl-kw">const</span> resetForm <span class="hl-op">=</span> <span class="hl-builtin">document</span>.<span class="hl-fn">getElementById</span>(<span class="hl-str">"reset-passkey-form"</span>);
	<span class="hl-kw">if</span> (resetForm) {
		resetForm.<span class="hl-fn">addEventListener</span>(<span class="hl-str">"submit"</span>, (event) <span class="hl-op">=&gt;</span> {
			event.<span class="hl-fn">preventDefault</span>();
			<span class="hl-fn">registerWebAuthnAccount</span>(resetForm);
		});
	}
});
</code></pre>
<p>In the HTML for the reset form, we we need to include the email in the same form field as the registration form (and also add an ID to the <code>&lt;form&gt;</code> element):</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">input</span> <span class="hl-attr">type</span>="<span class="hl-str">hidden</span>" <span class="hl-attr">name</span>="<span class="hl-str">user[email]</span>" <span class="hl-attr">value</span>=<span class="hl-str">{@user.email}</span> /&gt;
</code></pre>
<p>In the function for the reset route (which is changed to POST from PUT, to match the signup route), we take the credential ID and public key and use them to update the user’s credentials:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserResetPasswordController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">update</span>(conn, %{
        <span class="hl-str">"credential_id"</span> <span class="hl-op">=&gt;</span> credential_id,
        <span class="hl-str">"public_key_spki"</span> <span class="hl-op">=&gt;</span> public_key_spki
      }) <span class="hl-kw">do</span>
    credential_id <span class="hl-op">=</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>(credential_id)
    public_key_spki <span class="hl-op">=</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>(public_key_spki)
    <span class="hl-kw">case</span> <span class="hl-mod">Accounts</span><span class="hl-op">.</span><span class="hl-fn">reset_user_credentials</span>(conn<span class="hl-op">.</span><span class="hl-prop">assigns</span><span class="hl-op">.</span><span class="hl-prop">user</span>, credential_id, public_key_spki) <span class="hl-kw">do</span>
      <span class="hl-str">:ok</span> <span class="hl-op">-&gt;</span>
        conn
        <span class="hl-op">|&gt;</span> <span class="hl-fn">put_flash</span>(<span class="hl-str">:info</span>, <span class="hl-str">"Passkey reset successfully."</span>)
        <span class="hl-op">|&gt;</span> <span class="hl-mod">UserAuth</span><span class="hl-op">.</span><span class="hl-fn">log_in_user_without_redirect</span>(conn<span class="hl-op">.</span><span class="hl-prop">assigns</span><span class="hl-op">.</span><span class="hl-prop">user</span>)
        <span class="hl-op">|&gt;</span> <span class="hl-fn">json</span>(%{<span class="hl-str">status: </span><span class="hl-str">:ok</span>})

      {<span class="hl-str">:error</span>, <span class="hl-cmt">_</span>} <span class="hl-op">-&gt;</span>
        conn
        <span class="hl-op">|&gt;</span> <span class="hl-fn">put_flash</span>(<span class="hl-str">:error</span>, <span class="hl-str">"Error resetting passkey"</span>)
        <span class="hl-op">|&gt;</span> <span class="hl-fn">json</span>(%{<span class="hl-str">status: </span><span class="hl-str">:error</span>})
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>After updating, we log the user in if applicable and return JSON with the appropriate status.</p>
<p>The <code>reset_user_credentials</code> function works very similarly to the original reset password function that was part of the template: it deletes all the user’s existing sessions, and then removes their existing credentials and creates a new one:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">reset_user_credentials</span>(user, credential_id, public_key_spki) <span class="hl-kw">do</span>
    <span class="hl-mod">Ecto.Multi</span><span class="hl-op">.</span><span class="hl-fn">new</span>()
    <span class="hl-op">|&gt;</span> <span class="hl-mod">Ecto.Multi</span><span class="hl-op">.</span><span class="hl-fn">delete_all</span>(
      <span class="hl-str">:old_credentials</span>,
      <span class="hl-fn">from</span>(a <span class="hl-kw">in</span> <span class="hl-mod">UserCredential</span>, <span class="hl-str">where: </span>a<span class="hl-op">.</span><span class="hl-prop">user_id</span> <span class="hl-op">==</span> <span class="hl-op">^</span>user<span class="hl-op">.</span><span class="hl-prop">id</span>)
    )
    <span class="hl-op">|&gt;</span> <span class="hl-mod">Ecto.Multi</span><span class="hl-op">.</span><span class="hl-fn">insert</span>(
      <span class="hl-str">:new_credential</span>,
      <span class="hl-mod">UserCredential</span><span class="hl-op">.</span><span class="hl-fn">changeset</span>(%<span class="hl-mod">UserCredential</span>{}, %{
        <span class="hl-str">id: </span>credential_id,
        <span class="hl-str">public_key_spki: </span>public_key_spki,
        <span class="hl-str">user_id: </span>user<span class="hl-op">.</span><span class="hl-prop">id</span>
      })
    )
    <span class="hl-op">|&gt;</span> <span class="hl-mod">Ecto.Multi</span><span class="hl-op">.</span><span class="hl-fn">delete_all</span>(<span class="hl-str">:tokens</span>, <span class="hl-mod">UserToken</span><span class="hl-op">.</span><span class="hl-fn">user_and_contexts_query</span>(user, <span class="hl-str">:all</span>))
    <span class="hl-op">|&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">transaction</span>()
    <span class="hl-op">|&gt;</span> <span class="hl-kw">case</span> <span class="hl-kw">do</span>
      {<span class="hl-str">:ok</span>, <span class="hl-cmt">_</span>} <span class="hl-op">-&gt;</span> <span class="hl-str">:ok</span>
      {<span class="hl-str">:error</span>, <span class="hl-cmt">_</span>, changeset, <span class="hl-cmt">_</span>} <span class="hl-op">-&gt;</span> {<span class="hl-str">:error</span>, changeset}
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>It’s worth noting that this does have slightly weaker security properties than the <code>phx.gen.auth</code> reset password implementation. With that approach, leaking a password reset token does not necessarily result in an account takeover, since whoever obtained the leaked token may not know the target user’s email address. Since the auth template forces the user to re-login after a reset, this prevents someone without the email address from gaining access even if they change the password.</p>
<p>But since logging in with a passkey is functionally a single factor, resetting it means gaining access to the account. So, a leaked reset token gives the bearer control over the account. This is an argument against having a reset option, but whether this is a concern in practice depends on your specific circumstances.</p>
<h2>Conclusion</h2>
<p>As noted, this is not a complete implementation. There are a handful of places where I’ve left things unfinished since this isn’t meant to be production-level code. There are also a few places where there are security decisions that need to be made on a more contextual basis, that I’ve tried to note. And, of course, you wouldn’t really want to only permit signing in with passkeys and wholesale drop the passwords column from your database.</p>
<p>Nonetheless, I hope this has been a helpful look at how to implement passkeys in an Elixir/Phoenix application. You can find the complete repo <a href="https://git.shadowfacts.net/shadowfacts/phoenix_passkeys">here</a>, and it may also be useful to look at the <a href="https://git.shadowfacts.net/shadowfacts/phoenix_passkeys/commit/ce4f485dbc4e528bdd13a57b0d379012dd893338">specific commit</a> where passkey support was added.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>