Skip to content

Instantly share code, notes, and snippets.

@r-n-o
Last active August 12, 2024 16:36
Show Gist options
  • Save r-n-o/39eadea071f7488e0f90197e618059dd to your computer and use it in GitHub Desktop.
Save r-n-o/39eadea071f7488e0f90197e618059dd to your computer and use it in GitHub Desktop.
Passkeys, React Native, and Turnkey

Passkeys, React Native, and Turnkey

Back to the basics: what are passkeys again?

Passkeys are cryptographic key pairs generated and stored on secure hardware. Typically this is your Mac's secure enclave, your Android phone's Titan M2 chip, or a security key plugged in via USB.

  • Registration ("sign up") creates a new key pair
  • Authentication ("sign in") uses that key pair to sign a message with the private key

Why passkeys are different: they sync!

Webauthn has been a standard for quite a long time but it wasn't until a couple of years ago that "passkeys" came on. What makes them different? At their core they're "just" webauthn-compatible key pairs. One aspect is syncing:

  • When creating a passkey and store it on your Chrome profile, you can use that passkey on any device you're logged into (with the same Chrome profile)
  • When creating a passkey and store it on your iCloud Keychain, you have access to it from any Apple device you're logged into
  • There's also cross-ecosystem bridges: you can use passkeys cross-device with a QR code flow
  • There's also cross-ecosystem escape hatches: iOS lets you select the Chrome app as a source of passkeys, and Chrome lets you pick where you want to save your passkey. If you're on Mac you can store your Chrome-created passkey into the system's (iCloud-synced) keychain

What a world we live in!

Interacting with passkeys on the web

Creating and using passkeys on the web is straightforward: browsers offer APIs to do it!

  • navigator.credentials.create creates a passkey
  • navigator.credentials.get prompts the user to select a passkey to sign a message

This doesn't require any backend! Proof/demo: https://passkeyapp.tkhqlabs.xyz/

An important security feature of passkeys: they're domain-bound, to prevent phishing. Passkeys created on passkeyapp.tkhqlabs.xyz won't be usable on turnkey.com for example. Browsers prevent this.

Interacting with passkeys on native platforms

Android On Android the CredentialManager supports this with CreatePublicKeyCredentialRequest and GetCredentialRequest (see https://developer.android.com/training/sign-in/passkeys#sign-in)

iOS On iOS same deal: APIs to create and use passkeys are available.

  • ASAuthorizationPlatformPublicKeyCredentialProvider(…).createCredentialRegistrationRequest for passkey creation
  • ASAuthorizationPlatformPublicKeyCredentialProvider(…).createCredentialAssertionRequest for passkey usage

See these docs for more info. And this app for a mini demo.

What about Turnkey?

Turnkey has a pretty cool TypeScript SDK: https://github.com/tkhq/sdk/tree/main/packages/http

It provides a type-safe client to call the Turnkey API. This won't work if you're working in Kotlin (Android) or Swift (iOS). There's no API client for these languages yet.

Enters: React Native, which lets you write your app in Typescript once and compile it into native code automatically!

And enters also: react-native-passkey, a library for react native applications to call the right native methods on ios and Android

If you're writing a React Native app, all you need to do is call the library's unified abstraction: Passkey.register for passkey creation, and Passkey.authenticate for usage. One API, nicer names. Win.

Demo: https://github.com/r-n-o/passkeyapp-bare is an app which does just that. It's built with React native, and it uses the library to create/use passkeys. No Turnkey integration though! :(

That's great but how do you call the Turnkey API? Enters (last character): stampers. They're abstract interfaces to sign Turnkey requests. We already have:

  • @turnkey/api-key-stamper: to use API keys to sign requests
  • @turnkey/iframe-stamper: to use credentials within an iframe to sign requests
  • @turnkey/webauthn-stamper: to use webauthn (passkey) credentials to sign requests

Webauthn-stamper expects to have access to the web APIs (remember: navigator.credentials.create, navigator.credentials.get). These APIs aren't available in React Native environments because apps don't run in a browser, they're native apps!

So what do we need? A new stamper which wraps the react-native-passkey API. Tadaaa: https://www.npmjs.com/package/@turnkey/react-native-passkey-stamper

Demo: let's run https://github.com/r-n-o/passkeyapp on the simulator!

The kicker: linking apps and web domains

I didn't expect this going in: passkeys on native apps aren't app-bound. They're domain bound as well! This is a bit annoying at first (you have to configure a web domain to use passkeys natively!), but it unlocks very interesting flows.

For example: I can create an app linked to wallet.tx.xyz domain to allow users to log into their account from mobile using the same passkey they used to create the account on the web.

And the other way around: we can use the native app to create a passkey and an associated sub-organization, and this passkey can be available on the web as long as it's synced properly!

Configuration is done separately per ecosystem, but surprisingly similar:

  • iOS expects a JSON file at the domain root (/.well-known /apple-app-site-association) : example
  • Android expects a JSON file at the domain root (/.well-known/assetlinks.json): example

Demo: let's run https://github.com/r-n-o/passkeyapp on by actual device (unfortunately passkey syncing doesn't work in iOS simulators yet), and open up https://passkeyapp.tkhqlabs.xyz/!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment