This guide describes how to set up a passkey app that allows to share passkeys with other devices using cloud sync (Vue.js, Flutter iOS & Android, Golang).
Vincent
Created: November 16, 2023
Updated: July 14, 2024
Our mission is to make the Internet a safer place, and the new login standard passkeys provides a superior solution to achieve that. That's why we want to help you understand passkeys and its characteristics better.
1. Introduction: Cross-Platform Passkeys Sharing
2. Prerequisites: Syncing Passkeys Across Devices
2.1 Knowledge Base for Passkey Sync Example
3. Architecture for Cross-Platform Passkey Sharing App
3.1.3 Flutter (iOS &Android) Native App
3.2 Passkey Sharing Implications
3.2.1 Unified Relying Party ID to Allow Passkey Sharing Across Apps
3.2.2 Association Files to Link Native Apps with Web Apps
4. Setting Up the Vue.js Web App
4.1 Initialization & Integration of the Vue.js Web App
4.1.1 Simplified Setup with the Corbado Web Component
4.1.2 Embedded Web Component Location is Important
4.2 Passkey Authentication Flow in the Vue.js Web App
5. Setting Up the Flutter Native App
5.1 Initialization & Integration of the Flutter Native App
5.2 Passkey Authentication Flow in the Flutter Native App
6. Analyzing the Passkey Sync Process
7. Troubleshooting and Lessons Learned
7.1 Local Testing Issues for the Flutter App (Emulator & Physical Devices)
7.2 Defining the Right Relying Party ID
7.3 Native vs. WebView Implementation of Passkeys in Native Apps
We strongly recommend to read our article on Relying Party IDs before diving deeper into this article, as it really helps to understand things better.
Passkeys are still pretty new in the tech world. Theyre a step up from WebAuthn, which has been around for quite a while, mainly because passkeys can be synced / shared across different platforms and devices. This means you can share passkeys between web apps and native apps , even across different platforms or ecosystems, which is pretty cool!
But here's the catch - while there are some tutorials out there for implementing passkeys on web apps, theres a big gap when it comes to tutorials for native apps written in Kotlin, Swift, or cross-platform frameworks like Flutter or React Native. And a guide that takes you through setting up both a web app and a native app to share passkeys? We couldnt find a single one, so we decided to create this guide.
This blog post is for you if youre looking to set up something similar or just want totry out passkey sharing functionality. If youre only interested in a web app tutorial, please see our guides for popular frameworks and languages (React, Angular, Vue.js , Next.js, Nuxt.js, SvelteKit, PHP Symfony, Node.js, Python Flask, Java Spring, JavaScript), and for a Flutter tutorial, see here. Were not going to deep-dive into the code because that part isnt too tricky. The real challenge , and where theres a lack of documentation, is in the setup and configuration.
As a decorative example, were going to use the setup of the Corbado developer panel. Here, we have a web app and recently also created native Flutter apps for iOS and Android that you can download and play around with to see the result from a users perspective - we practice what we preach, or in tech terms, We eat ourown dog food.
Sharing passkeys in this blog post should not be mistaken with sharing passkeys within e.g. an Apple account to let other family members access your passkeys. For more details on this process, please check this blog post.
You can find the completed code for the Flutter app in this GitHub repository.
Recent Articles
Before we dive into setting up our applications, lets ensure were all on the same page regarding the foundational knowledge and tools needed to follow the tutorial.
Native & Web Apps Basics: A fundamental understanding of native app development and general web app architecture is crucial.
Passkeys & WebAuthn: A solid understanding of passkeys and WebAuthn is essential since well be delving into advanced configurations and setups. Ensure youre familiar with the basic processes and functionalities of passkeys and WebAuthn to smoothly navigate through the tutorial. In case you need some refreshments on certain topics, please have a look at our other detailed blog posts:
Flutter:
Well be using Flutter for developing the native app. Ensure you have it installed and are familiar with its basic functionalities. In Flutter, we use the corbado_auth package, which internally uses the passkeys package.
Vue.js & Golang:
The frontend of our web app will be crafted using Vue.js, while Golang will power the backend. Familiarity with these technologies, especially in the context of web development, will be beneficial.
Corbado Web Component:
In the web app, well be integrating the Corbado web component. If you havent worked with it before, we recommend going through one of our tutorials available for various languages / frameworks, including React, Angular, Vue.js, Next.js, Nuxt.js, and Svelte , to get a comprehensive overview.
Become part of our Passkeys Community for updates and support.
JoinEmbarking on a journey to implement passkey sharing between web and native applications necessitates a thorough understanding of the underlying architecture. Lets delve into the core concepts and architectural components that will be the backbone of our setup.
Lets first have a look at the different components that we have in our setup:
Application Server:
The application server is written in Golang (while any other programming language or framework would have worked as well). It manages all the incoming requests and take care of all the business logic. It can be accessed at https://pro-1.frontendapi.corbado.io or alternatively via the CNAME https://auth.corbado.com we have set that points to the same destination.
WebAuthn Server:
The central part for passkey authentication / WebAuthn is the WebAuthn server, which is responsible for managing authentication requests. Contrary to its name, its not a server on an own host but rather an SDK / library / package that runs within the scope of the Application Server. Read this blog post to understand more about WebAuthn server implementations. The WebAuthn server is not directly reachable but only via the application server.
Database:
The database is used to store information about the users and passkeys. In our example, were using MySQL (any other database could have worked as well). The database is not publicly reachable.
Vue.js Application:
We use Vue.js as our frontend framework (any other framework or plain HTML / JavaScript would have worked as well). You can access the Vue.js application at https://app.corbado.com
Corbado Web Component:
The Vue.js application implements the Corbado web component, which comes with a lot of passkey features out-of-the-box simplifying the integration process and ensuring seamless interaction with the backend.
A straightforward Flutter app is utilized. The Flutter app implements the corbado_auth package (and thus the passkeys package), which facilitates communication with the backend. The package name for the Flutter (iOS & Android) native app is com.corbado.developerpanel.app.
The WebAuthn server, which manages the public keys, is the same for the web and native application. This ensures that the authentication data is consistently available across the two platforms, facilitating seamless passkey sharing and authentication.
Therefore, the Relying Party ID (rpID) needs to be the same for passkeys created either in the web or native app.
The Relying Party ID (rpID) we define in our example is set to corbado.com. To help you find out the suitable Relying Party ID (rpID) for your case, please have a look into our detailed guide here.
The apple-app-site-association file for iOS apps and the assetlinks.json file for Android apps are pivotal in establishing and verifying the relationship between the web and native applications. The files are hosted on our server at
They ensure that a created passkey on either of the two applications can be used on the other one. Its important that server address (https://corbado.com) where the files are stored is the same like the Relying Party ID (corbado.com).
Lets have a look on the content of these two files:
apple-app-site-association:
Currently, there are two entitlements set up: applinks and webcredentials.
applinks is not necessarily needed for passkeys but still set for better UX. It allows to directly open suitable links for the Corbado developer panel in the Flutter native app.
webcredentials is the important one that must be set for passkey synchronization to work properly. As the apple-app-site-association file is hosted at the location of the Relying Party ID server (here at https://corbado.com with the Relying Party ID corbado.com) and downloaded during the app install, the native app is associated to the Relying Party ID.
The value stored in here (the same value as the appID in the applinks entitlement) consists of two elements. The first part is the Application Identifier Prefix (here: T9A667JL6T) and your Bundle Identifier (here: com.corbado.developerpanel.app). The Application Identifier Prefix can be obtained by going to your Apple Developer Certificates, Identifier & Profiles associated with your Apple Developer account.
Note: When creating your Bundle Identifier, make sure that the Associated Domains capability is enabled:
assetlinks.json
As you can see, there are two entries set to associate the web app and the native app.
The first entry is for the target with namespace android_app (our Flutter native app), while the second one is for the target with namespace web (our Vue.js web app). Both entries contain each the same two required relations delegate_permission/common.handle_all_urls and delegate_permission/common.get_login_creds. Moreover, the native app entry contains the used package name (here: com.corbado.developerpanel.name) and the SHA-256 signing certificate fingerprint (here e.g.: 8B:BF:39:60:61:89:30:A4:45:F3:D7:09:1E:7B: 1B:05:0F:8A:FD:AF:24:EB:F1:EB:2E:3D:13:88:09:FC:79:59), while the web app contains the URL to the backends host (here the CNAME https://auth.corbado.com to the Frontend API URL https://pro-1.frontendapi.corbado.io).
Often generating or extracting the SHA-256 signing certificate causes issues or is confusing. Please have a look at this blog post, where we help you in great detail to get your right values.
Understanding this architecture and its passkey implications lays the foundation to the setup and configuration of our web and native application.
Now, we have a look on the practical implementation of the Vue.js web application. Lets see how the web application can be quickly setup by using the Corbado web component, which saves a lot of development time, as it comes with intelligent passkey management, device detection and passkey UI / UX out- of-the-box. Besides that, were also using Corbados session management to have a simple, secure and efficient session management as well.
While we wont delve deep into the detailed setup of the web application in this guide, our other guides provide a comprehensive walkthrough for a basic setup. For our Corbado developer panel example, please have a look at this Vue.js passkeys guide with our web component. We encourage you to explore it for a thorough understanding of the initial steps.
One of the most crucial steps in the Vue.js passkeys guide is defining the Relying Party ID. You can do this in the Corbado developer panel. In there, we set the Relying Party ID for our web app (and thus also for our native app) to corbado.com.
The web component itself can be embedded at any location of the web app. We decided to host our developer panel on the subdomain app.corbado.com. This domain also embeds the Corbado web component as we wanted to allow our users a sign-up and login from here. As outlined in our Relying Party ID guide, adding the web component to a subdomain (app.corbado.com) of the Relying Party ID (corbado.com) is fine (but not the other way around).
Additionally, and more as a side note, we also embedded the web component on our external website www.corbado.com, which is also fine as it still matches the Relying Party ID (corbado.com), because subdomains can access the passkeys of the corresponding root domains. Please take a look at this detailed blog post to better understand the domain matching of the Relying Party ID.
After we followed the guide above and deployed our application accordingly (for details regarding the go live of a web app, see also our go live guide), we are ready to try the passkey authentication process on our web application. In the following, we explain what happens in the background and which checks are being passed.
Our test device is a MacBook with activated Apple iCloud Keychain where we stored the passkeys in. This allows us to later also used them on an iPhone that is synced in the same Apple iCloud Keychain.
In this section, weve laid the groundwork for the web application and showed how to create a passkey for the web app. With the Relying Party ID accurately defined and a basic understanding of the authentication flow, were now ready to explore the configuration of the Flutter application in the next section.
Lets have a deeper look now into the setup of the Flutter application. For speeding up the passkeys implementation process in Flutter, we make use of the corbado_auth package (internally it uses the passkeys package) that comes with all the required settings for passkey authentication on iOS and Android and has basic other authentication SDK features like session management.
Simplified Setup with the corbado_auth package:
While we wont delve deep into the detailed setup of the Flutter app in this guide, this Flutter guide provides a comprehensive walkthrough for a basic setup. We encourage you to explore it for a thorough understanding of the initial steps.
See our full GitHub repository here for details.
To make things easily reproducible for you, we published a simple Corbado developer panel app into the App Store and Play Store. Please download the corresponding app for your platform.
In the following, we describe the process of using the passkey created before on our MacBook and using it for our login in the iOS app. A synchronization of this passkey to the Android app is not possible, as it is synced in the iCloud Keychain and thus Android smartphones wouldnt have access to it. To allow for such a cross-platform passkey synchronization today, you would need a cross-platform synced password manager, like Dashlane or 1Password (which needs to be installed on both devices) or you can alternatively use the cross-platform passkey authentication with QR codes and Bluetooth.
Congrats, thats it! Youve successfully managed to create a passkey on one device (MacBook + web app), sync it via iCloud Keychain to another device (iPhone + native iOS app) and use it for authentication. This process would have also worked the other way round.
In case this does not work take a look at our troubleshooting guide. Alternatively, you can look up the existing passkey on iOSvia Settings -> Passwords on your iPhone and search for corbado.com (or whatever Relying Party ID youre looking for). This should show you all available passkeys for corbado.com: example pictures. This approach works on macOS, Windows and Android as well (with slight adjustments in the steps to be done).
macOS: Settings -> Passwords
Android: Settings -> Password Manager
Windows: Settings -> Passkey
In the previous step, we successfully used the passkey for cross-device synchronization between a MacBook and an iPhone. Depending on where you synced the passkey during passkey creation the following scenarios would also have been possible:
Scenario 1: Create passkey in web app on iPhone / Android and log in with it in native app on iPhone/Android (or vice versa)
iOS: If you create the passkey in the web app, it will be automatically stored in your iCloud Keychain but also locally on your device. When then opening the native app, iOS will try to access the secure enclave with the locally stored passkeys and make use of it for the login process in the native app.
Android: The process is here very much the same to iOS. The passkey is stored during creation in the Google Password manager (and synced here), but also locally in the device itself. In the login process of the native app, the local passkey is then used to authenticate in the native app.
In case creating a passkey does not work on your iOS or Android device, you probably have not activated the necessary settings for your platform. Please see here for troubleshooting guidance and further help.
Scenario 2: Create passkey in a device that uses passkey-syncable password manager and use it for login on other device with passkey-syncable password manager
Many modern non-OS password managers (e.g. 1Password or Dashlane) have recently been upgraded to also support passkeys meaning that they take the place of Apple iCloud Keychain or Google Password Manager. Therefore, the passkeys are synced in the non-OS password manager and every passkey-ready device that has the non-OS password manager installed gets access to the passkey and can use it for authentication.
Scenario 3: Create passkey on Android device 1 web app and use it for login on Android device 2 native app (same would also be valid with iPhone)
iOS: If you create a passkey on your iPhone 1 and would have a second iPhone 2, the passkey will be accessible from the second iPhone, as long as both devices are synced in the same Apple iCloud Keychain.
Android: The process here again is very much the same compared to iOS. The only difference is that the passkeys are not stored in the iCloud Keychain but instead stored in the Google Password Manager, which is also available on other Android smartphones from the same Google account.
Scenario 4: Create passkey in native / web app on Android / iPhone and use it for login in web app on Windows 11 via QR code & Bluetooth
If you create a passkey on your Android or iPhone (no matter if its within a native app or web app), you can use this for cross-platform authentication (also called hybrid transport), e.g. to use it in a web app running on a Windows 11. When trying to log into the web app on Windows 11, theres the option to display a QR code that you can scan with your Android or iPhone. Using a Bluetooth proximity check and a tunnel under the hood the passkey from the Android / iPhone is used for authentication and you will be logged into the web app on the Windows device. Please read this blog post if you are interested in details on this process.
Subscribe to our Passkeys Substack for the latest news, insights and strategies.
SubscribeWeve spent the last months on providing tools and tutorials to make developers lives easier when it comes to passkey implementation in general but especially to passkeys in native apps and sharing them across devices in platforms.
Once youve got everything set up correctly, its a pretty smooth process from UX view. Coming there might cause some headaches though due to the lack of documentation and references. Therefore, we created a list of common issues that weve encountered in the past to help you avoid these issues.
Difficulties in testing passkey login on simple devices led us to develop the Flutter package, aiding developers in circumventing these issues. Explore our troubleshooting section for insights.
Identifying and accurately setting up the right relying party ID, which this blog post aims to guide through.
Deciding on the implementation method of passkeys in a native app, be it via WebView or natively. Our guide provides detailed guidance on this aspect.
As we wrap up this comprehensive guide, lets recap the most important points ensuring a solid understanding of implementing passkey sharing between web and native applications. At first, we took a deeper look into the architectural setup of a such an application landscape and how things need to be configured. Then, we looked at the implementation aspects for a Vue.js web app and Flutter native app and in the end, we provided some guidance in case of troubles and lessons learned.
Your feedback is invaluable to us! Share your thoughts, experiences, and join our passkeys community for further discussions, collaborations, and to stay updated on the latest developments in passkey authentication. Also we keep you posted via our passkeys Substack that you can subscribe to for free.
Table of Contents
Enjoyed this read?
🤝 Join our Passkeys Community
Share passkeys implementation tips and get support to free the world from passwords.
🚀 Subscribe to Substack
Get the latest news, strategies, and insights about passkeys sent straight to your inbox.
We provide UI components, SDKs and guides to help you add passkeys to your app in <1 hour
Start for free
Recent Articles
WebAuthn Resident Key: Discoverable Credentials as Passkeys
Vincent - September 28, 2023
WebAuthn Conditional UI (Passkeys Autofill) Technical Explanation
Vincent - October 20, 2023
WebAuthn Relying Party ID (rpID) & Passkeys: Domains & Native Apps
Vincent - September 21, 2023