<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jan Gerle</title>
    <description>The latest articles on DEV Community by Jan Gerle (@jgerle).</description>
    <link>https://dev.to/jgerle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F651220%2F12926cad-578c-41ad-be1d-b60e4e33c035.jpeg</url>
      <title>DEV Community: Jan Gerle</title>
      <link>https://dev.to/jgerle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jgerle"/>
    <language>en</language>
    <item>
      <title>Passkeys for native iOS app authentication</title>
      <dc:creator>Jan Gerle</dc:creator>
      <pubDate>Mon, 19 Jul 2021 21:09:55 +0000</pubDate>
      <link>https://dev.to/hanko/how-to-support-apple-icloud-passkeys-with-webauthn-on-ios-15-and-macos-monterey-part-2-1jb5</link>
      <guid>https://dev.to/hanko/how-to-support-apple-icloud-passkeys-with-webauthn-on-ios-15-and-macos-monterey-part-2-1jb5</guid>
      <description>&lt;p&gt;&lt;em&gt;In this second part of our passkeys series, we will be modifying Apple's Shiny iOS app to make use of the same passkeys (a.k.a. WebAuthn credentials) that we created with our &lt;a href="https://dev.to/hanko/how-to-support-apple-icloud-passkeys-with-webauthn-on-ios-15-and-macos-monterey-37h3"&gt;web app from part one&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So far we saw that the passkeys are automatically synced between your Apple devices and you could use them in Safari with the web app. Now it is time to make them accessible in native iOS apps as well.&lt;/p&gt;

&lt;p&gt;Remember: This will allow you to register to a website with Touch ID in Safari, then download the app on your iPhone, and sign in to that app again with Touch ID or Face ID without using a password at all. It also works the other way around, of course.&lt;/p&gt;

&lt;p&gt;What are passkeys again, you ask? It's a brand new technology for secure passwordless authentication &lt;a href="https://developer.apple.com/videos/play/wwdc2021/10106/"&gt;introduced by Apple at WWDC21&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A word of warning:&lt;/strong&gt; We were pretty excited when we heard that Apple finally supports platform-wide WebAuthn on iOS. So when we started to work on the app we quickly found out that Apple's APIs are still a bit... &lt;em&gt;beta&lt;/em&gt;. We ran into some WebAuthn-related bugs and had to create an "experimental" version of our Authentication API to work around them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Please activate the &lt;em&gt;experimental features&lt;/em&gt; in the Hanko Authentication API. To accomplish this, log into your &lt;a href="https://console.hanko.io/"&gt;Hanko Console&lt;/a&gt;, select the Relying Party, and navigate to the "General Settings". On the right side you will find the button to activate the experimental features.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We have created bug reports at Apple and provided them with the details. It seems like other developers have encountered these errors as well, so we assume that Apple will fix them sooner or later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Running the web app on HTTPS
&lt;/h3&gt;

&lt;p&gt;We assume that you have the &lt;a href="https://dev.to/hanko/how-to-support-apple-icloud-passkeys-with-webauthn-on-ios-15-and-macos-monterey-37h3"&gt;web app from part one&lt;/a&gt; up and running on your own HTTPS-capable host with a valid certificate. Use &lt;a href="https://letsencrypt.org/"&gt;Let's Encrypt&lt;/a&gt; for a start. Otherwise the mobile app integration will fail due to the required &lt;em&gt;Associated Domains&lt;/em&gt; capability.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Associated domains establish a secure association between domains and your app so you can share credentials or provide features in your app from your website.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;-- &lt;a href="https://developer.apple.com/documentation/xcode/supporting-associated-domains"&gt;Apple Developer documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Apple Developer account
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;Associated Domains&lt;/em&gt; capability is only available to you if you are on a paid Apple Developer plan. As a team member you need access to the Developer Resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Xcode 13 (Beta)
&lt;/h3&gt;

&lt;p&gt;You need &lt;a href="https://developer.apple.com/download/"&gt;Xcode 13&lt;/a&gt; to work on this project and enjoy beautiful APIs like "&lt;em&gt;ASAuthorizationPlatformPublicKeyCredentialRegistration&lt;/em&gt;" 👀.&lt;/p&gt;

&lt;h3&gt;
  
  
  iOS or iPadOS 15 (Dev Beta)
&lt;/h3&gt;

&lt;p&gt;To make use of the new Keychain-sync feature you need a device with iOS or iPadOS 15 which is currently available at &lt;a href="https://developer.apple.com/de/support/beta-software/"&gt;Apple's Developer Beta program&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This device needs to be configured in Xcode, obviously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iCloud needs to be set up with the same Apple ID as on your Mac!&lt;/strong&gt; Otherwise there will be no syncing...&lt;/p&gt;

&lt;h3&gt;
  
  
  Reminder: Enable Platform Authenticator Syncing on your Apple devices
&lt;/h3&gt;

&lt;p&gt;In iOS 15, turn on the Syncing Platform Authenticator switch under Settings &amp;gt; Developer. The Developer menu is available on your device when you set it up as a development device in Xcode.&lt;/p&gt;

&lt;p&gt;In macOS Monterey, go to Safari &amp;gt; Preferences, click the Advanced tab, and select the “Show Develop menu in menu bar” option. Then enable the Develop &amp;gt; Enable Syncing Platform Authenticator menu item in Safari.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the iOS app to work
&lt;/h2&gt;

&lt;p&gt;After having completed all of the above prerequisites we can proceed to configure the Shiny mobile app.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone &lt;a href="https://github.com/teamhanko/apple-wwdc21-ios-demo-shiny/"&gt;the repo of our WebAuthn-enabled Shiny app&lt;/a&gt; from Github&lt;/li&gt;
&lt;li&gt;Open it up in Xcode, select the Shiny Project and:

&lt;ol&gt;
&lt;li&gt;In the &lt;em&gt;Signing &amp;amp; Capabilities&lt;/em&gt; pane choose your team from the Team drop-down menu to let Xcode automatically manage your provisioning profile.&lt;/li&gt;
&lt;li&gt;Add the &lt;em&gt;Associated Domains&lt;/em&gt; capability, and specify your domain with the webcredentials service (e.g. &lt;code&gt;webcredentials:yourdomain.com&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;Get your &lt;em&gt;Team ID&lt;/em&gt; (e.g. &lt;code&gt;1ABC23DEF4&lt;/code&gt;) and the generated &lt;em&gt;Bundle Identifier&lt;/em&gt; (e.g. &lt;code&gt;com.example.apple-samplecode.Shiny1ABC23DEF4&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;In your web app you need to setup the Apple Site Association by adding the following line to your &lt;code&gt;config/config.yaml&lt;/code&gt;file:
&lt;code&gt;iosAppId: "&amp;lt;Team ID&amp;gt;.&amp;lt;Bundle Identifier&amp;gt;"&lt;/code&gt;, e.g. &lt;code&gt;iosAppId: "1ABC23DEF4.com.example.apple-samplecode.Shiny1ABC23DEF4"&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Save the file and restart the webapp&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;In the mobile app in the Accountmanager file change the domain variable to your web app's domain:
&lt;code&gt;let domain = "yourdomain.com"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Attach your iPhone or iPad to your Mac and select it as execution environment in Xcode&lt;/li&gt;
&lt;li&gt;In Xcode, clean the build folder and start the mobile app&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first start might take a while, but after a few seconds you should have the Shiny app running on your device. If you have created an account in the web app using your Mac, you should be presented with a sign-in dialogue, asking for Touch ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  The source code
&lt;/h2&gt;

&lt;p&gt;Basically we have taken Apple's Shiny app and taught it to sign in users with passkeys. We have removed most of the superfluous password parts, but other than that tried not to modify it too much so you can clearly see the steps needed.&lt;/p&gt;

&lt;p&gt;The magic happens in the Shared folder, especially in the &lt;code&gt;AccountManager.swift&lt;/code&gt; file - so let's start there. The original Shiny gives us some hints on where it needs to be amended.&lt;/p&gt;

&lt;p&gt;The only external library we have added to the code is &lt;a href="https://github.com/Alamofire/Alamofire"&gt;Alamofire&lt;/a&gt; to comfortably make HTTP requests.&lt;/p&gt;

&lt;p&gt;In the steps above you have already modified the first line of code in the AccountManager: you have set the domain of your web app. You have set up the Apple site association prior as well. These steps are crucial, as they are the foundation to share credentials between your web app and the native app we are looking at right now. The domain name in this case will be used to create login challenges and validate them via http calls to your web app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signing in
&lt;/h2&gt;

&lt;p&gt;The first function we need to amend is the &lt;code&gt;signInWith()&lt;/code&gt; function. To sign a user in we first need to fetch a unique challenge from the Hanko Authentication API via our web app. This challenge will be signed with the passkey and returned to the web app for validation, again utilizing the Hanko Authentication API.&lt;/p&gt;

&lt;p&gt;We have our first encounter with Apple's new API right at the beginning of the &lt;code&gt;signInWith()&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;publicKeyCredentialProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationPlatformPublicKeyCredentialProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;relyingPartyIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a provider to work with a passkey stored in the iCloud Keychain. It takes our domain as the only option for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the challenge
&lt;/h3&gt;

&lt;p&gt;Next, we are fetching the challenge from the server with the &lt;code&gt;getAuthenticationOptions()&lt;/code&gt; function and have the result available in the &lt;code&gt;assertionRequestOptions&lt;/code&gt; variable in the following function block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;getAuthenticationOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;assertionRequestOptions&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;getAuthenticationOptions()&lt;/code&gt; function itself basically calls our web app's &lt;code&gt;/authentication_initialize&lt;/code&gt; endpoint, we have used that endpoint in the browser flow as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;getAuthenticationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;completionHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CredentialAssertion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;AF&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/authentication_initialize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;responseDecodable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CredentialAssertion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual challenge is delivered to us Base64URL encoded. The object used to store the response is the &lt;code&gt;CredentialAssertion&lt;/code&gt; object defined in the &lt;code&gt;Models.swift&lt;/code&gt; file. Go check it out - it is pretty straight forward. As Swift does only deal with plain Base64 we are using a helper function to convert the challenge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assertionRequestOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publicKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decodeBase64Url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the &lt;code&gt;decodeBase64Url()&lt;/code&gt; function at the end of the &lt;code&gt;AccountManager.swift&lt;/code&gt; file. It takes an Base64URL string as an input. Basically it replaces a few characters and sorts out the padding. As a result it gives us plain Base64.&lt;/p&gt;

&lt;p&gt;Once we have our challenge we can proceed to create the assertion request. This request will be signed with the passkey. We are using the &lt;code&gt;publicKeyCredentialProvider&lt;/code&gt; which we have created earlier for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;assertionRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;publicKeyCredentialProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCredentialAssertionRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we check if we require user verification and enable it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;userVerification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assertionRequestOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publicKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userVerification&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;assertionRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userVerificationPreference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationPublicKeyCredentialUserVerificationPreference&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userVerification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please admire Apple's new beautiful API at this point for a second: &lt;code&gt;ASAuthorizationPublicKeyCredentialUserVerificationPreference&lt;/code&gt;. Feels a bit like the German Donaudampfschiffahrtskapitänsmütze...&lt;/p&gt;

&lt;p&gt;&lt;em&gt;User Verification&lt;/em&gt; is ...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The technical process by which an authenticator locally authorizes the invocation of the authenticatorMakeCredential and authenticatorGetAssertion operations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, if a relying party, a.k.a. your web app, requires User Verification, it actually triggers the &lt;strong&gt;&lt;em&gt;local&lt;/em&gt;&lt;/strong&gt; authentication in the means of Touch ID, Face ID, or PIN/password to unlock your Keychain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Please keep in mind that your biometrics, your PIN, or your password WILL NEVER LEAVE YOUR DEVICE!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Signing the challenge with your passkey
&lt;/h3&gt;

&lt;p&gt;To finally sign our login challenge (a.k.a. assertion request) we create an &lt;code&gt;ASAuthorizationController&lt;/code&gt; and hand it over our &lt;code&gt;assertionRequest&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pass in any mix of supported sign in request types.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;authController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;authorizationRequests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;assertionRequest&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;authController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
&lt;span class="n"&gt;authController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;presentationContextProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
&lt;span class="n"&gt;authController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;performRequests&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you compare this to the original Shiny app you can see that you could also send password credentials at this point. We have removed this part for the sake of focus, clarity and because our web app purposefully does &lt;em&gt;not&lt;/em&gt; do passwords.&lt;/p&gt;

&lt;p&gt;It is important to understand that our &lt;code&gt;AccountManager&lt;/code&gt; class implements the &lt;code&gt;ASAuthorizationControllerDelegate&lt;/code&gt; interface. The official Apple documentation defines &lt;em&gt;delegation&lt;/em&gt; as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Delegation is a design pattern that enables a class to hand off (or “delegate”) some of its responsibilities to an instance of another class.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So when the &lt;code&gt;performRequests()&lt;/code&gt; method is called at the end, our own &lt;code&gt;authorizationController()&lt;/code&gt; function is being called, because we have delegated it to ourselves with the &lt;code&gt;authController.delegate = self&lt;/code&gt; two lines earlier. We have two versions of the &lt;code&gt;authorizationController()&lt;/code&gt;, one for the success case and one for the failure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;authorizationController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didCompleteWithAuthorization&lt;/span&gt; &lt;span class="nv"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorization&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;authorizationController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didCompleteWithError&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's have a look at the happy-path :) During the &lt;code&gt;authController.performRequests()&lt;/code&gt; execution, right before our &lt;code&gt;authorizationController()&lt;/code&gt; is being called, the user has been presented with the prompt to unlock the Keychain, asking for Touch ID, Face ID, or the local device's PIN. In our case the passkey connected with the domain has been granted access to and was used to sign the challenge.&lt;/p&gt;

&lt;p&gt;We have an &lt;code&gt;ASAuthorization&lt;/code&gt; object now and it contains the asserted credentials, stored in the credential property - it's an &lt;code&gt;ASAuthorizationPlatformPublicKeyCredentialAssertion&lt;/code&gt; in Apple API speak :D These asserted credentials are being sent to your web app's backend using the &lt;code&gt;sendAuthenticationResponse()&lt;/code&gt; function to verify them with the Hanko API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;authorizationController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didCompleteWithAuthorization&lt;/span&gt; &lt;span class="nv"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorization&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;credentialAssertion&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationPlatformPublicKeyCredentialAssertion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A credential was used to authenticate: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;credentialAssertion&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// After the server has verified the assertion, sign the user in.&lt;/span&gt;
        &lt;span class="nf"&gt;sendAuthenticationResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;credentialAssertion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;didFinishSignIn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On success, the &lt;code&gt;didFinishSignIn()&lt;/code&gt; function is being called, which in turn triggers the presentation of the screen for a signed-in user – today's Shiny!&lt;/p&gt;

&lt;p&gt;And while we are at it: the first occasion that triggers the &lt;code&gt;signInWith()&lt;/code&gt; function is the &lt;code&gt;viewDidAppear()&lt;/code&gt; method of the &lt;code&gt;SignInViewController&lt;/code&gt;. So right at the start of our app it tries to sign in the user, using a passkey that is registered for your &lt;code&gt;domain&lt;/code&gt;. If there is no matching passkey on your iPhone or iPad, nothing visible happens. In our debug environment we can see an error message in the console in that case, courtesy to our &lt;code&gt;logger.log()&lt;/code&gt; call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registering a new account
&lt;/h2&gt;

&lt;p&gt;To be able to register a new passkey-protected account, we create the function &lt;code&gt;signUpWith()&lt;/code&gt; in the &lt;code&gt;AccountManager&lt;/code&gt; class. This function essentially takes a username, tries to register an account with our web app and subsequently creates a new passkey for it.&lt;/p&gt;

&lt;p&gt;We start off like the &lt;code&gt;signInWith()&lt;/code&gt; function by creating a &lt;code&gt;publicKeyCredentialProvider&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;signUpWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ASPresentationAnchor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;authenticationAnchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;publicKeyCredentialProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationPlatformPublicKeyCredentialProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;relyingPartyIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;getRegistrationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To register our desired username with the web app, we are using the &lt;code&gt;getRegistrationOptions()&lt;/code&gt; method defined in our &lt;code&gt;AccountManager&lt;/code&gt; class. It makes use of our web app's &lt;code&gt;/registration_initialize&lt;/code&gt; endpoint with the username as GET parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;getRegistrationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;AF&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/registration_initialize?user_name=&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking once again at the happy path, in case of success we receive a &lt;code&gt;creationRequest&lt;/code&gt; object. We are picking the challenge and user ID from it, converting them from Base64URL to Base64 in the process. We use both to create our actual credentials creation request, based on the &lt;code&gt;publicKeyCredentialProvider&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;signUpWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ASPresentationAnchor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* ... */&lt;/span&gt;

    &lt;span class="nf"&gt;getRegistrationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;creationRequest&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;creationRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publicKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decodeBase64Url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;userID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;creationRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publicKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decodeBase64Url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;registrationRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;publicKeyCredentialProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCredentialRegistrationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User verification and device trust model
&lt;/h3&gt;

&lt;p&gt;In case our web app would require some sort of &lt;em&gt;attestation&lt;/em&gt; (device or authenticator trust model, see below) or &lt;em&gt;user verification&lt;/em&gt; we extract them from the &lt;code&gt;creationRequest&lt;/code&gt; just like the challenge and user ID before and apply them to the &lt;code&gt;registrationRequest&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;getRegistrationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;creationRequest&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;attestation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;creationRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publicKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attestation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;registrationRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attestationPreference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationPublicKeyCredentialAttestationKind&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;userVerification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;creationRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publicKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;authenticatorSelection&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userVerification&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;registrationRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userVerificationPreference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationPublicKeyCredentialUserVerificationPreference&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userVerification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Attestation serves the purpose of providing a cryptographic proof of the authenticator attributes to the relying party in order to ensure that credentials originate from a trusted device with verifiable characteristics.&lt;/p&gt;

&lt;p&gt;-- &lt;a href="https://docs.hanko.io/guides/attestation"&gt;Hanko documentation: Authenticator trust model&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having assembled the &lt;code&gt;registrationRequest&lt;/code&gt; we now proceed with the &lt;code&gt;authController&lt;/code&gt; like before during sign in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;getRegistrationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;creationRequest&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="cm"&gt;/* ... */&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;authController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;authorizationRequests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;registrationRequest&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;authController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
    &lt;span class="n"&gt;authController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;presentationContextProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
    &lt;span class="n"&gt;authController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;performRequests&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having delegated the &lt;code&gt;authorizationController&lt;/code&gt; to ourselves (see above), this time the switch catches on the &lt;code&gt;ASAuthorizationPlatformPublicKeyCredentialRegistration&lt;/code&gt; once the user has granted permission to create the new passkey. &lt;code&gt;sendRegistrationResponse&lt;/code&gt; has the web app verify and finalize the registration on the &lt;code&gt;/authentication_finalize&lt;/code&gt; endpoint and calls the app's &lt;code&gt;didFinishSignIn()&lt;/code&gt; on success.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;authorizationController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didCompleteWithAuthorization&lt;/span&gt; &lt;span class="nv"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorization&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;credentialRegistration&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;ASAuthorizationPlatformPublicKeyCredentialRegistration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A new credential was registered: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;credentialRegistration&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;sendRegistrationResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;credentialRegistration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;didFinishSignIn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This leads us to our Shiny screen again. Voilá, that's it!&lt;/p&gt;

&lt;p&gt;I hope you have enjoyed this article. If you have any questions: just post a comment below!&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>webauthn</category>
      <category>passwordless</category>
    </item>
    <item>
      <title>Passkeys for web authentication</title>
      <dc:creator>Jan Gerle</dc:creator>
      <pubDate>Mon, 21 Jun 2021 05:44:59 +0000</pubDate>
      <link>https://dev.to/hanko/how-to-support-apple-icloud-passkeys-with-webauthn-on-ios-15-and-macos-monterey-37h3</link>
      <guid>https://dev.to/hanko/how-to-support-apple-icloud-passkeys-with-webauthn-on-ios-15-and-macos-monterey-37h3</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the first part of a two-part series on &lt;strong&gt;passkeys&lt;/strong&gt;, as introduced by Apple at WWDC21. In this article, we will walk you through the creation of a simple web app for registration and authentication using passkeys on Apple devices with the new "passkeys in iCloud Keychain" sync feature. In part 2 of this guide, we will cover adding a sample iOS app to your setup from the guide, demonstrating a seamless user experience with passkeys across web and mobile.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Target Audience: Developers who want to try out passkeys with their website and / or app and, for that, need to adopt WebAuthn on their server.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Say hello to Apple’s embracement of WebAuthn
&lt;/h2&gt;

&lt;p&gt;Apple &lt;a href="https://developer.apple.com/videos/play/wwdc2021/10106/" rel="noopener noreferrer"&gt;announced at WWDC21&lt;/a&gt; that WebAuthn credentials will be available as “passkeys” in the iCloud Keychain, as well as the availability of system-wide WebAuthn APIs on iOS, iPadOS, and macOS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9padit8d4bkq8u26w2p.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9padit8d4bkq8u26w2p.gif" alt="Apple Passkey Sign In Sequence"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;
Passkey sign-in sequence on an Apple iPad with iOS 15
&lt;/p&gt;

&lt;h3&gt;
  
  
  What is WebAuthn?
&lt;/h3&gt;

&lt;p&gt;Passkeys are based on WebAuthn, a capability of your operating system that enables your device to store private key material (the WebAuthn credentials) and generate signatures with them to authenticate you against a web server. Sounds complicated, I know, but for the end user this breaks down to using Touch ID or Face ID on websites and apps instead of passwords. At the same time, behind the scenes, the WebAuthn protocol allows for a very strong, unphishable, cryptographic multi-factor authentication mechanism that can replace all other current second-factor methods like OTP apps or generators, SMS passcodes, or even smartcards, while being far more secure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Passkeys = (synced) WebAuthn credentials
&lt;/h3&gt;

&lt;p&gt;While the WebAuthn API has been available on all major platforms – including iOS and macOS – for some time, Apple's new "Passkeys in iCloud Keychain" feature is attempting to solve WebAuthn's biggest remaining pain point: &lt;strong&gt;device loss&lt;/strong&gt;, i.e., account recovery. The synchronization of WebAuthn credentials across all devices associated with the same Apple ID enables true passwordless accounts that do not need to fall back to less secure authentication or recovery methods like passwords if you want to sign in to a website or app on a new device. Once enrolled, users can sign in with Face ID and Touch ID on all their devices (Apple-only, for now) without worrying at all about creating or memorizing a password or becoming the victim of a password-related attack like Phishing.&lt;/p&gt;

&lt;h3&gt;
  
  
  System-wide WebAuthn APIs for websites and native apps
&lt;/h3&gt;

&lt;p&gt;The other WWDC announcement, system-wide WebAuthn APIs on iOS and macOS, is also very welcome, because the APIs enable apps and websites from the same service (i.e., the same URL) to access the same WebAuthn credentials on a device. You register in the app, and can use the same passkey via Touch ID or Face ID seamlessly on the service's website as well (and vice-versa). Another result of the APIs is that other browsers than Safari (once they implement the new APIs) can access the credentials as well. Until now, only Safari supported system-level WebAuthn credentials on iOS, iPadOS, and macOS. Apple is only catching up here though, as this feature is already present on Windows 10 ("Windows Hello") and Android.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adopt WebAuthn on your server
&lt;/h2&gt;

&lt;p&gt;In their WWDC announcement video, Apple demonstrates the creation and seamless synchronization of passkeys across devices. They even show that WebAuthn works with iOS Apps using the same passkey. How to create the server part is left opaque, though. Actually, it is just an item in their list of "Next steps" without further explanation.&lt;/p&gt;

&lt;p&gt;‍&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5e6f5d845cbcc543de2d520a%2F60c8e6addce44d36329054e1_adopt_webauth_on_your_server_highlighted.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5e6f5d845cbcc543de2d520a%2F60c8e6addce44d36329054e1_adopt_webauth_on_your_server_highlighted.jpg" alt="Screenshot of Apple's passkey video, showing the phrase Adopt WebAuthn on your server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;
"Adopt WebAuthn on your server, will ya?"
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this guide, you will:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learn how to set up a simple web server that supports WebAuthn and therefore, passkeys&lt;/li&gt;
&lt;li&gt;Create a sample website with WebAuthn registration and authentication&lt;/li&gt;
&lt;li&gt;Build and run a demo setup showing cross-device, end-to-end passwordless authentication on iOS 15 / macOS Monterey devices&lt;/li&gt;
&lt;li&gt;Bonus: As it is based on pure WebAuthn, the demo will also work on Windows 10 and Android 7+ devices (only without the passkey in iCloud Keychain sync feature)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What do you need to implement passkey login and iCloud Keychain sync?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two Apple devices to actually sync the passkeys, e.g., an iPhone with iOS 15 and a Mac with Monterey. Use Safari on both of them.&lt;/li&gt;
&lt;li&gt;A WebAuthn-capable web app (we’ll get to that 😀)&lt;/li&gt;
&lt;li&gt;A WebAuthn / FIDO2 server component (we happily provide the Hanko Authentication API for that 🚀)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again – in case you are looking for the iOS app case, i.e., sharing passkeys between apps and websites, this will be the content of the second part of this guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Celebrating the ceremonies
&lt;/h2&gt;

&lt;p&gt;Some context first: WebAuthn relies on two ‘ceremonies’, the credential registration and the actual authentication. In the &lt;a href="https://www.w3.org/TR/webauthn-2/" rel="noopener noreferrer"&gt;WebAuthn spec&lt;/a&gt;, they are called ‘attestation’ and ‘assertion’, but we will stick to registration and authentication.&lt;/p&gt;

&lt;p&gt;During registration, a unique public/private keypair is being generated. The private key – a.k.a. the passkey – is stored in the Keychain and the corresponding public key is being stored on the server. In our case at hand, the registration takes place only once, during initial user account registration. In a real world scenario, you would enable your users to add multiple WebAuthn credentials to their account on their profile page, e.g., USB/NFC Security Keys or other WebAuthn-capable devices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgptca1cf1cbf0ay4vxz6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgptca1cf1cbf0ay4vxz6.png" alt="Register a passkey"&gt;&lt;/a&gt;&lt;/p&gt;
Passkey creation during account registration



&lt;p&gt;Following the registration, whenever a user wants to log in to the service’s website or app, instead of providing a username and password, the user requests authentication with the passkey, using the WebAuthn protocol. In our demo case, the button will just say “Login”, no other form fields are required. The user does not even need to provide a username – ain’t that cool?! No more lost usernames!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmkw9km1xki7etkxddwb8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmkw9km1xki7etkxddwb8.png" alt="Signing in using a passkey"&gt;&lt;/a&gt;&lt;/p&gt;
Signing in using a passkey



&lt;p&gt;Access to the passkey is protected on your device with your preferred mechanism: Face ID, Touch ID, or a PIN. The passkey itself never leaves your device during registration or authentication, it is only being used locally for creating a digital signature that will be validated with the public key on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s get to work!
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Enable Platform Authenticator Syncing
&lt;/h3&gt;

&lt;p&gt;First of all, enable Platform Authenticator Syncing on your Apple devices. In iOS 15, turn on the Syncing Platform Authenticator switch under Settings &amp;gt; Developer. The Developer menu is available on your device when you set it up as a development device in Xcode.&lt;/p&gt;

&lt;p&gt;In macOS Monterey, go to Safari &amp;gt; Preferences, click the Advanced tab, and select the “Show Develop menu in menu bar” option. Then enable the Develop &amp;gt; Enable Syncing Platform Authenticator menu item in Safari.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the WebAuthn-enabled web application
&lt;/h3&gt;

&lt;p&gt;We will be using a simple html/JavaScript website with a Go backend for this demonstration. Of course you can use whatever language you are comfortable with on the server side. We choose Go, as you only need a few libraries to get the job done and it is easy to read even if you are not a Go expert.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A quick word on good security practices&lt;/em&gt;: This is a demo application. To keep things clean, we will not provide a lot of error handling or input sanitizing. You should not use this code in production environments.&lt;/p&gt;

&lt;p&gt;To process WebAuthn requests in a web app, you need a WebAuthn server component, sometimes also called a "FIDO2 Server". This server is dealing with the key management on the application’s behalf, almost like a PKI. There are some open source implementations for that available on GitHub. Surely the fastest way to get WebAuthn up and running is using our Cloud-hosted &lt;a href="https://www.hanko.io/authentication-api" rel="noopener noreferrer"&gt;Hanko Authentication API&lt;/a&gt;. For that you can create a free account at &lt;a href="https://console.hanko.io/" rel="noopener noreferrer"&gt;Hanko Dev Console&lt;/a&gt; and set it up according to our &lt;a href="https://docs.hanko.io/gettingstarted" rel="noopener noreferrer"&gt;Getting Started Guide&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting up the project
&lt;/h4&gt;

&lt;p&gt;We assume that you have Go installed. If not, now is the right time to &lt;a href="https://golang.org/doc/install" rel="noopener noreferrer"&gt;do so&lt;/a&gt;. Another tool you need is Git – we just assume that it is installed.&lt;/p&gt;

&lt;p&gt;Next you need to clone our repository, which contains a small ready-made web app that uses WebAuthn credentials for authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/teamhanko/apple-wwdc21-webauthn-example
&lt;span class="nb"&gt;cd &lt;/span&gt;apple-wwdc21-webauthn-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what’s in there?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We are keeping most of the backend code in the &lt;code&gt;main.go&lt;/code&gt; file for the sake of simplicity, with two supporting models in a subfolder.&lt;/li&gt;
&lt;li&gt;In the &lt;em&gt;config&lt;/em&gt; subfolder, you will find a config file named &lt;code&gt;config.template.yaml&lt;/code&gt;. Rename it to &lt;code&gt;config.yaml&lt;/code&gt; and complete it with your Hanko API credentials.&lt;/li&gt;
&lt;li&gt;The three html templates needed for the frontend reside in the &lt;em&gt;templates&lt;/em&gt; folder.&lt;/li&gt;
&lt;li&gt;In the &lt;em&gt;assets&lt;/em&gt; subfolder there is a file named &lt;code&gt;app.js&lt;/code&gt;. This is our registration and authentication procedure, which will be triggered by the "Sign in" and "Register" buttons. We will take a look at these functions later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s start with the main.go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// main.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gin-contrib/sessions"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gin-contrib/sessions/cookie"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gin-gonic/gin"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gofrs/uuid"&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="s"&gt;"github.com/sirupsen/logrus"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/teamhanko/apple-wwdc21-webauthn-example/config"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/teamhanko/apple-wwdc21-webauthn-example/models"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/teamhanko/hanko-go/webauthn"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty straight forward: we import the Go http and strings libraries, along with the Gin session middleware, the cookie library and the Gin request router. They enable us to create http endpoints to communicate with and to create cookie-based sessions for signed-in users. &lt;/p&gt;

&lt;p&gt;To create unique ids for our users, we choose UUID and import a library for that.&lt;/p&gt;

&lt;p&gt;Last but not least, we need the Hanko Go SDK, the corresponding configuration, and the two supporting models.&lt;/p&gt;

&lt;p&gt;The Go app itself has a few http endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/assets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./assets"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;// static assets like images&lt;/span&gt;
  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StaticFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/favicon.ico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./assets/favicon.ico"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// a favicon :)&lt;/span&gt;
  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StaticFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./index.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// the main screen w/ login button&lt;/span&gt;
  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StaticFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/register"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./register.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// the registration form&lt;/span&gt;

  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/registration_initialize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;// step 1 for registration&lt;/span&gt;
  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/registration_finalize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c"&gt;// step 2 for registration&lt;/span&gt;

  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/authentication_initialize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// step 1 for authentication&lt;/span&gt;
  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/authentication_finalize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;// step 2 for authentication&lt;/span&gt;

  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;// the protected content, served after login&lt;/span&gt;
  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c"&gt;// the logout url&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides some static content, we can see the four endpoints needed for the two WebAuthn ceremonies: registration and authentication.&lt;/p&gt;

&lt;p&gt;You might have noticed the &lt;strong&gt;initialize/finalize&lt;/strong&gt; pattern here: Whenever we are in the WebAuthn context, we first have to do an initialization with the FIDO Server. Then we need to communicate with the Authenticator (i.e., your Mac or iPhone) using Hanko’s JavaScript SDK and pass the result to the finalize endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  User signup – the registration ceremony
&lt;/h3&gt;

&lt;p&gt;The first two endpoints handle the registration ceremony. When the user enters the desired username and hits the “Register” button, the JavaScript function &lt;code&gt;do_reg()&lt;/code&gt; in our &lt;code&gt;app.js&lt;/code&gt; calls the &lt;code&gt;/registration_initialize&lt;/code&gt; endpoint of the web app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This function will be called by the “Register” button&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;do_reg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;?user_name=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regInitResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/registration_initialize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;creationOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;regInitResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint will check the desired username, create a UUID, and return a JSON object which is contained in our JavaScript constant &lt;em&gt;creationOptions&lt;/em&gt;. Let’s take a look at the backend code that creates said JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt; 
  &lt;span class="c1"&gt;// Create the request options for the Hanko API&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;webauthn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NewRegistrationInitializationUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;authenticatorSelection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;webauthn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NewAuthenticatorSelection&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;
    &lt;span class="nc"&gt;WithUserVerification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webauthn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VerificationRequired&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nc"&gt;WithAuthenticatorAttachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webauthn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nc"&gt;WithRequireResidentKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;webauthn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NewRegistrationInitializationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nc"&gt;WithAuthenticatorSelection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authenticatorSelection&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nc"&gt;WithConveyancePreference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webauthn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PreferNoAttestation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Get the registration request from the Hanko API with the given &lt;/span&gt;
  &lt;span class="c1"&gt;// request options&lt;/span&gt;
  &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiErr&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;apiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InitializeRegistration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First off, the code above picks up the ID and username. We need them for the call to the Hanko API. Then we set a few parameters for the WebAuthn credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User Verification&lt;/strong&gt;: Required – This triggers the Authenticator to ask for Face ID, Touch ID or a PIN whenever the new Passkey is to be used. Your device decides which mechanism is active. We want multi-factor authentication!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authenticator Attachment&lt;/strong&gt;: Platform – We want your Mac or your iPhone as authenticator device. Another option would be to require an USB Security Key for example.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resident Key&lt;/strong&gt;: True – This feature is also referred to as “Discoverable Credential” and it enables us to authenticate without a username, just by providing the passkey. Pretty convenient. We want that, so we switch it on!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conveyance Preference&lt;/strong&gt;: Prefer no Attestation: This determines if we want to receive so called attestation information. Think of it as a certificate about the capabilities of the Authenticator. You would be using that in a scenario with advanced security needs, e.g., in an online banking scenario. This is not the case here, so we switch it off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Hanko API creates a correctly formatted representation of these parameters for us, which our JavaScript picks up as mentioned above. Our app can now pass them to the browser’s WebAuthn API using Hanko’s JavaScript SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;‍&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authenticatorResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hankoWebAuthn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;creationOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;hankoWebauthn.create()&lt;/code&gt; function will trigger a native dialogue in Safari to grant permission to create a new passkey by unlocking your Keychain. Once completed, we POST the authenticator’s response to the backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registrationResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/registration_finalize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authenticatorResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend at &lt;code&gt;/registration_finalize&lt;/code&gt; receives this response and calls the Hanko API again, completing the registration ceremony.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="o"&gt;...&lt;/span&gt;
 &lt;span class="c"&gt;// Send the authenticator response to the Hanko API&lt;/span&gt;
 &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/registration_finalize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Parse the authenticator response&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; 
    &lt;span class="n"&gt;webauthn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseRegistrationFinalizationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;apiClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FinalizeRegistration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// on success create the user session&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this is successful, the browser will be redirected to the &lt;code&gt;/content&lt;/code&gt; endpoint of the web app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;registrationResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;registrationResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;
          &lt;span class="nf"&gt;showRegError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// redirect on success&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Well done! You are now registered with your passkey&lt;/strong&gt; 🥳&lt;/p&gt;

&lt;p&gt;As you have just registered your passkey, the application now considers you as “signed in”. Because of Apple’s new syncing feature, the passkey is now already available on your companion device – let’s assume that this is your iPhone.&lt;/p&gt;

&lt;p&gt;To move on to the next step, press the “Logout” button in the upper right corner. This takes you to the &lt;code&gt;/logout&lt;/code&gt; endpoint, terminating your session, and immediately redirecting you to the start page. Now we can proceed to the second ceremony.&lt;/p&gt;

&lt;h3&gt;
  
  
  User login – the authentication ceremony
&lt;/h3&gt;

&lt;p&gt;The only thing we need to create the ultimate login experience is: A "Sign in" button 😉 and a rather simple JavaScript function &lt;code&gt;do_auth()&lt;/code&gt; to trigger the login process. No need for a separate username field, as we are using the domain name and the UUID as our common identifier behind the scenes. Passkeys are fixed to a specific domain.&lt;/p&gt;

&lt;p&gt;Let’s have a look at the first half of the do_auth() function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;do_auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authInitResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/authentication_initialize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;authInitResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authenticatorResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hankoWebAuthn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function first calls the backend’s &lt;code&gt;/authentication_initialize&lt;/code&gt; endpoint, which creates request options like we did during registration. The resulting request options object is passed to Safari’s WebAuthn API using Hanko’s Javascript SDK function &lt;code&gt;hankoWebAuthn.get(authOptions)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The corresponding backend code using the Hanko SDK is rather short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Get an authentication request from the Hanko API&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/authentication_initialize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Create the request options&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;webauthn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewAuthenticationInitializationRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;WithUserVerification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webauthn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VerificationRequired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;WithAuthenticatorAttachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webauthn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Get the authentication result from the Hanko API with the &lt;/span&gt;
    &lt;span class="c"&gt;// given request options&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;apiClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitializeAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like at registration, a native OS dialogue will show up. You are being presented with a list of registered passkeys and can confirm usage with a simple click.&lt;/p&gt;

&lt;p&gt;Again, the passkey is being used to sign the request, the key itself will not leave your device! Once a passkey has successfully been used, the resulting Authenticator response is sent to the Hanko API for validation, using the backend’s &lt;code&gt;/authentication_finalize&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;Now to the second half of the do_auth() function in our frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authenticationResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/authentication_finalize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authenticatorResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;authenticationResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;authenticationResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// login successful&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend code takes the response from the authenticator and validates it against the Hanko API. In case of success, a session is being created and the frontend code redirects to our private &lt;code&gt;/content&lt;/code&gt; page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Send the authenticator response to the Hanko API&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/authentication_finalize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Parse the authenticator response&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;webauthn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseAuthenticationFinalizationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="c"&gt;// Send the authenticator reponse to the Hanko API for validation&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;apiClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FinalizeAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// If no error occurred during the authenticator response validation,&lt;/span&gt;
    &lt;span class="c"&gt;// create a session for the given user&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Credential&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  That’s it!
&lt;/h3&gt;

&lt;p&gt;You are signed in, using only a passkey that is protected and unlocked by your preferred local authentication mechanism: Face ID, Touch ID or a PIN. Try the login with your iPhone, it just works without registering again – no passwords involved!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhj1a2td0ve49dzri7f0b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhj1a2td0ve49dzri7f0b.png" alt="Passkey list on an iPad"&gt;&lt;/a&gt;&lt;/p&gt;
The generated passkey is automatically available on your other devices with the same Apple ID





&lt;h2&gt;
  
  
  See the demo in action
&lt;/h2&gt;

&lt;p&gt;Of course we have prepared a running example for you, just in case. You can find it &lt;a href="https://apple-passkey.demo.hanko.io/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And you can access the &lt;a href="https://github.com/teamhanko/apple-wwdc21-webauthn-example" rel="noopener noreferrer"&gt;complete source code&lt;/a&gt; of this project on our GitHub.&lt;/p&gt;

&lt;p&gt;Now, as WebAuthn is a widely adopted internet standard, this demo also works using &lt;a href="https://caniuse.com/webauthn" rel="noopener noreferrer"&gt;other browsers and platforms&lt;/a&gt;. Give it a try, invite your friends, your mom, and your co-workers to join the fun and feel the difference of a convenient and highly secure login experience. WebAuthn powered by the Hanko API 💪&lt;/p&gt;

&lt;p&gt;See you for part 2 of this guide where we will add Apple's Shiny iOS app to our little demo setup. Stay tuned...&lt;/p&gt;

&lt;p&gt;‍&lt;/p&gt;

&lt;p&gt;If you enjoyed this guide, have a question, or any thoughts how we can improve, please comment or &lt;a href="https://www.hanko.io/contact" rel="noopener noreferrer"&gt;reach out&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>javascript</category>
      <category>webauthn</category>
      <category>passwordless</category>
    </item>
  </channel>
</rss>
