Experimental implementation of Passkey Autofill in Go

56
6 min readMar 21, 2024

--

Passkey Autofill is a new type of passwordless sign-in UI. It is a privacy preserving list UI element that is rendered by the browser (or the OS platform in the case of native apps), in cooperation with the platform authenticator, on username and/or password fields that have the “webauthn” value included in the that using UI element. The UI element provides a list of passkeys available for the Relying Party (RP) on the local device and may also provide an option to kick off Cross-Device Authentication or use a FIDO2 Security key.

In this article, I introduce an experimental implementation that supports Passkey Autofill using a backend implemented in Go.

This simple implementation provides for developers to try out Passkey Autofill in their environment. Therefore, a detailed description of the underlying technical specifications of Passkey is omitted. Please note that if you want to check various behaviors according to your requirements, you should understand the technical specifications on which Passkey is based, such as FIDO2 (Web Authentication + CTAP2).
For example, Passkey is defined as a FIDO credential.

From a technical standpoint, passkeys are FIDO credentials that are discoverable by browsers or housed within native applications or security keys for passwordless authentication.
(Ref: https://fidoalliance.org/passkeys/)

Also, FIDO2 is a specification consisting of the Web Authentication Specification and the CTAP2 Specification.

The FIDO2 specifications are the World Wide Web Consortium’s (W3C) Web Authentication (WebAuthn) specification and FIDO Alliance’s corresponding Client-to-Authenticator Protocol (CTAP).
(Ref: https://fidoalliance.org/fido2/)

Understanding terms such as Conditional UI and Discoverable Credential in WebAuthn is also important.

This is why this is called the Conditional UI (or more commonly, the autofill UI) mode of WebAuthn
(Ref: https://passkeys.dev/docs/use-cases/bootstrapping/#a-note-about-user-verification)

Note: Historically, client-side discoverable credentials have been known as resident credentials or resident keys.
(Ref: https://www.w3.org/TR/webauthn-3/#discoverable-credential)

If you need to understand Passkey, reviewing the documentation for the specification as described above is useful.

Note that this implementation uses the following two libraries:

Steps to check sample implementation

Run the following commands to obtain the sample code and start it.

$ git clone https://github.com/kg0r0/passkey-autofill-example.git
$ cd passkey-autofill-example
$ go run .

Access http://localhost:8080 and register Passkey.

Access http://localhost:8080/login to authenticate. You will now see that the Passkey you registered earlier is displayed.

The registration and authentication process is now complete.

As the diagram of the Web Authentication API specification shows, there are defined flows for registration and authentication, respectively. In addition, backend and frontend processes are required for each flow. The endpoint design of the implementation described in this article is based on the Server Requirements and Transport Binding Profile.

Registration

First, the backend generates options to call the Web Authentication API (navigator.credentials.create()) in the browser. Then, the front end calls navigator.credentials.create()and sends the results to the backend. Finally, the frontend validates the result.

Registration Flow (Ref: https://www.w3.org/TR/webauthn-3/#sctn-api)

There is no specific configuration associated with Passkey Autofill during the registration step. You can proceed with the implementation by referring to the following guidance for the library you are using.

On the other hand, the following requirements are also listed in the specifications, so change the settings of the libraries to be used if necessary.

The user verification result (conveyed in authenticator data flags) will reflect the actual user verification result and should always be validated against your requirements on the server.
(Ref: https://passkeys.dev/docs/use-cases/bootstrapping/#a-note-about-user-verification)

The Passkey Autofill UI also uses the residentKey option to create a discoverable credential so that the Relying Party does not need to identify the user first.

residentKey, of type DOMString
Specifies the extent to which the Relying Party desires to create a client-side discoverable credential. For historical reasons the naming retains the deprecated “resident” terminology.
(Ref: https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-residentkey)

You may also consider how the backend data will be stored based on the description in the specification.

User Handle
A user handle is an identifier for a user account, specified by the Relying Party as user.id during registration. Discoverable credentials store this identifier and MUST return it as response.userHandle in authentication ceremonies started with an empty allowCredentials argument.
(Ref: https://www.w3.org/TR/webauthn-3/#user-handle)

The complete registration-related implementations in the sample repository can be seen in attestation.go and templates/index.html.

Authentication

First, the backend generates options to call the Web Authentication API (navigator.credentials.get()) in the browser. Then the front end calls navigator.credentials.get()and sends the results to the back end. Finally, the front end validates the results.

Authentication Flow (Ref: https://www.w3.org/TR/webauthn-3/#sctn-api)

You can proceed with the implementation by referring to the description of the methods used in the authentication step of each library.

On the other hand, the authentication step requires specific settings such as those described below for Passkey Autofill.

To support the autofill UI for passkeys, make sure to:
1.
Add the username and webauthn value to any existing autocomplete annotations on the username input field as shown below in the example.
2. On page load, check to see if autofill UI (conditional mediation) is available using an if statement, then call navigator.credentials.get() with mediation: “conditional” and userVerification: “preferred”.
(Ref: https://passkeys.dev/docs/use-cases/bootstrapping/)

Note that the implementation of SimpleWebAuthn complements some of these. So you need to make a few adjustments, such as executing them in the onload event of the page.

Also, Relying Party invokes navigator.credentials.get()with an empty allowCredentials argument in authentication, it is not necessary to identify the user first.

A Client-side discoverable Public Key Credential Source, or Discoverable Credential for short, is a public key credential source that is discoverable and usable in authentication ceremonies where the Relying Party does not provide any credential IDs, i.e., the Relying Party invokes navigator.credentials.get() with an empty allowCredentials argument. This means that the Relying Party does not necessarily need to first identify the user.
(Ref: https://www.w3.org/TR/webauthn-3/#discoverable-credential)

You may also adjust how you identify users on the backend as needed.

If the user account to authenticate is not already identified, then the Relying Party MAY leave this member empty or unspecified. In this case, only discoverable credentials will be utilized in this authentication ceremony, and the user account MAY be identified by the userHandle of the resulting AuthenticatorAssertionResponse.
(Ref: https://www.w3.org/TR/webauthn-3/#dictionary-assertion-options)

The complete authentication-related implementations in the sample repository can be seen in assertion.go and templates/login.html.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

56
56

No responses yet

Write a response