passkey - A developer guide

A quick technical introduction and exploration of passkeys, W3C webauthn and FIDO

Shekhar Jha
6 min readOct 17, 2023

In May 2022, three major companies — Apple, Google, and Microsoft — announced their support for a set of standards for passwordless authentication created by FIDO Alliance and W3C. This new technology, popularized by Apple as “passkey” on macOS 13 (Ventura) & iOS 16 during WWDC 2022. In October 2022, Google announced support for passkey on chrome (M108) & android (Android OS 9). Microsoft has supported passkeys since Windows 10, version 1809 (released in October 2018) through webauthn API. This brief historical overview should provide perspective on how recent the phenomenon of passkey is, and it’s worth noting that you are not late to the party. Additionally, please be aware that the core standards have been supported at varying levels by a majority of browsers for a while and have a proven track record.

If you are new to passkeys & WebAuthn in general, I would recommend checking out the various excellent resources available. My personal favorite for learning how this technology works is the Auth0 WebAuthn demo. It also features a WebAuthn debugger that you can use to experiment with different settings. If you want to delve deeper into the standard, visit https://webauthn.jhash.com, a website I created to simplify the understanding and implementation of the WebAuthn standard from a developer’s perspective.

Passkey registration (generated using webauthn.me & ezgif.com)
Passkey authentication (generated using webauthn.me & ezgif.com)

Passkey is loosely defined by a combination of the following standards and features:
1. Webauthn standard defines how the web application can interact (through javascript) with browser to:
a) register and store a new credential, and
b) authenticate using a stored credential.
Most developers integrating with passkeys would be dealing with this standard, which is the focus of this article.
2. CTAP2 standard defines how the browser interacts with an authenticator (security key or device with camera) over NFC, BLE, and USB. This standard is typically intended for developers who are building browsers/OS or interfaces for security keys (such as Yubico, Titan), smart cards. We will not be covering this in the article.
3. Passkey storage and sync — Before the introduction of passkeys, any credentials created based on the standards mentioned above by the browser and authenticator (like a security key) would be stored on the authenticator itself (within the security key or secure enclave on a laptop/phone). These credentials are now referred to as device-bound passkeys. However, this implied that credentials would be lost if the corresponding authenticator (or device containing authenticator) was lost, resulting in a account recovery challenge. Passkey introduced the idea of syncing the credentials stored in the secure enclave on the device to the cloud and from there to other devices, addressing this shortcoming.

generated using imgflip.com

Code Please!

Let’s dig into some code.

Registering a credential

So what does it takes to register a new credential? You need a recent browser that supports FIDO authentication (check supported browser), developer tool, and a publicly accessible website over TLS/SSL (e.g. https://webauthn.jhash.com).

To register a new credential, copy and paste this code into the browser console, press enter, and then click on the page itself.

setTimeout(async() => {
var challengeVal = (new TextEncoder()).encode("ThisIsAVeryLongChallenge");
var userId = (new TextEncoder()).encode("ARandomUserIdThatDoesNotClashWithActualUserId");
var inputObj = {
publicKey: {
"rp": {
"name": "ACME"
},
"user": {
"id": userId,
"name": "aRandomUser",
"displayName": "A Random User"
},
"challenge": challengeVal,
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
]
}
}
navigator.credentials.create(inputObj).then(function(newCredInfo){credValue=newCredInfo; credValue}).catch(function(err){console.log("PasskeyError: " + err)})
}, 3000)

This captures the flow on a chrome browser.

Passkey registration — just javascript (generated using ezgif.com)

Let’s walk through the code

setTimeout(async() => { ...}, 3000)

This code initiates 3 seconds after pressing enter. This delay is necessary because Safari throws a PasskeyError: NotAllowedError: NotAllowedError: The document is not focused if the console is in focus when the navigator.credentials.create command is invoked. This marks your first encounter with the variations in implementation across different operating systems and browser combinations (we will cover similar variations in future articles).

var challengeVal = (new TextEncoder()).encode("ThisIsAVeryLongChallenge");
var userId = (new TextEncoder()).encode("ARandomUserIdThatDoesNotClashWithActualUserId");

Since certain values, such as challenge and user.id, need to be provided as array values, the input must be encoded.

var inputObj = {
publicKey: {
...
}
}

The provided code creates an object based on the standard. For a detailed analysis of what each setting means, please refer to https://webauthn.jhash.com.

navigator.credentials.create(inputObj).then(function(newCredInfo){
credValue=newCredInfo; credValue
}).catch(function(err){
console.log("PasskeyError: " + err)})
}

The navigator.credentials.create method is passed the inputObj, and it returns a promise that resolves with a new Credential instance based on the provided options, or null if no Credential object can be created. In exceptional circumstances, the Promise may reject.

Authenticate

Let’s attempt authentication using the credential we just created. As previously, copy and paste the code below, press enter, and click on page.

setTimeout(async() => {
var challengeVal = (new TextEncoder()).encode("ThisIsAVeryLongChallenge");
var inputObj = {
"publicKey": {
"challenge": challengeVal
}
}
navigator.credentials.get(inputObj).then(function(newCredInfo){credValue=newCredInfo}).catch(function(err){console.log("PasskeyError: " + err)})
}, 3000)
Passkey registration — just javascript (generated using ezgif.com)

Most of the code remains the same, except that the input has only one value, challenge, which is received from the server. The navigator.credentials.get method is used to initiate authentication.

Bonus

Google has announced that the passkey will be the default authentication method, replacing passwords. So, go ahead and create one for your account. After creating the passkey and testing it, logout of google or open www.google.com in incognito mode (this step is optional).
Now, copy and paste the authentication JavaScript and run it just like before.

setTimeout(async() => {
var challengeVal = (new TextEncoder()).encode("ThisIsAVeryLongChallenge");
var inputObj = {
"publicKey": {
"challenge": challengeVal
}
}
navigator.credentials.get(inputObj).then(function(newCredInfo){credValue=newCredInfo}).catch(function(err){console.log("PasskeyError: " + err)})
}, 3000)

What do you see? Did any of the passkeys you created on the device pop up?

Passkey on www.google.com

Now try this code.

setTimeout(async() => {
var challengeVal = (new TextEncoder()).encode("ThisIsAVeryLongChallenge");
var inputObj = {
"publicKey": {
"challenge": challengeVal,
"rpId": "google.com"
}
}
navigator.credentials.get(inputObj).then(function(newCredInfo){credValue=newCredInfo}).catch(function(err){console.log("PasskeyError: " + err)})
}, 3000)

Did you see your passkey pop up? So, how did "rpId": "google.com" change the process?
Google creates new passkeys for the domain google.com so that it can be used across all the Google websites. Since the passkey has been registered for the domain, during authentication, rpId has to be specified with the same value to ensure that browser would use the passkey for authentication.

We will cover this concept and many other ideas in part II of the series for developers. We will explore how you can learn from some of the largest implementations of passkey out there.

Conclusion

Passkeys are a hot topic in the consumer identity space. As an authentication developer, it is important to understand their capabilities and limitations across standards and implementations to integrate existing products or implement your own solution.

--

--

Shekhar Jha
Shekhar Jha

Written by Shekhar Jha

Focus area: Identity and access management (workforce and CIAM) and cloud security

Responses (1)