
Integrate passkeys into your Flutter apps with our open-source passkeys package
The following article describes how you can use the open-source Flutter passkeys package to add secure and user-friendly passkey authentication to your Flutter apps. You can bring your own passkey backend or make use of any authentication provider's passkey backend (e.g. Corbado has pre-built passkey server to start right away).
Hello Flutter developers,
With this tutorial, we want to introduce you to our new open-source Flutter passkeys package. The Flutter passkeys package offers passkeys / WebAuthn implementations for Android and iOS (more platforms to come) with emphasis on maximum flexibility and ease of use for developers. We want to help make the internet a safer place and are happy if you help by starting to ditch passwords and join the passkey-era.
Regarding passkeys, currently available Flutter packages are either complicated to set up or require an external passkey backend, that you have to set up and maintain on your own. This can be quite challenging and tedious (especially when you want to quickly prototype with passkeys).
Therefore, the main goals of the Flutter passkeys package are:
- Simplicity: Build an easy-to-use Flutter passkeys package that enables every Flutter developer to implement passkeys.
- Flexibility: Provide developers the possibility to integrate their own passkey backend.
- Support: There are a few pitfalls when setting up passkeys. We try to provide guidance and help for common mistakes.
Note: For realizing these main goals, no Corbado services are needed. You can simply use the passkeys package together with your own passkey backend.
This tutorial is structured as follows:
1.2 Flutter passkeys package architecture
2. Create Flutter application
4. (Optional) Configure your relying party server
5.1 Bind passkeys to specific domain
5.2 Integrate your own passkeys backend
6.1 Weak SHA-1 algorithm for Android keystore
6.2 Android screen lock not set up on virtual Android device
7.1 Use case: Prototype with passkeys
7.2 Use case: Use passkeys for your app with existing relying party server
7.3 Use case: Use passkeys for your app but you don't want to build your own relying party server
7.4 Use case: Build an app that supports more advanced use cases with passkeys
7.5 Use case: Use Firebase together with passkeys
The code of this tutorial is taken from the Flutter passkeys package example, which you can find on GitHub and on pub.dev.
Here’s a preview of the running example app in Android:

To better understand the architecture of the Flutter passkeys package, we first look at passkeys and how they work.
In a traditional authentication system (e.g., a password-based one), a developer sets up a backend including a database where user credentials (e.g., email + passwords) will be stored after the sign up. The password must only be known by the user and developers must take precautions to protect these passwords in the backend (e.g. hashing, salting).
Now, let’s come to passkeys. For a passkey authentication system, there is also a sign up and a login flow. Moreover, there is also a backend involved which is called “relying party server”. During the sign up flow, the user creates a public / private key pair (by using e.g. Face ID or Touch ID). The public key is shared with the relying party server and is permanently stored there. The private key is kept securely on the user’s device.
During every login, the relying party server is asked for a cryptographic challenge by the client (the native iOS or Android app). The challenge can only be solved with the private key. Access to that private key is protected with Face ID or Touch ID. As soon as the user proves his identity using his biometrics, the device can sign the challenge and sends it back to the relying party server. For a more detailed explanation, visit passkeys.dev.
Next, we’ll explain some key concepts of the Flutter passkeys package (non-exhaustive architecture). In a nutshell, the package supports you by abstracting and connecting two very different parts in the passkey world:
- The authenticator: The authenticator provides OS functionalities that can create a passkey and sign passkey challenges by asking the user for his biometrics. It's written in OS-specific languages (e.g.Swift, Java).
- The relying party server: While the server itself is hosted on a remote server the Flutter app needs to access it as a client. The passkeys package provides a general interface that can be implemented by users of the package to use their own backend as a relying party server.
The passkeys package connects these two parts and exposes them in the PasskeyAuth class to enable a sign up and login. It even provides a default implementation (with Corbado) for the relying party server interface that enables you to quickly start testing.

A more detailed view at the architecture:
1. Native plugins
1.1. passkeys_android: The native implementation for Android that is responsible for receiving the challenge, verifying the user and sending a response to the challenge back afterwards. All user interaction on Android is handled by the passkeys_android plugin.
1.2. passkeys_ios: The native implementation for iOS that is responsible for receiving the challenge, verifying the user and sending a response to the challenge back afterwards. All user interaction on iOS is handled by the passkeys_ios plugin.
2. passkeys package
2.1. passkeys_auth: The passkey_auth connects the authenticator (2.2) and the relying_party_server (2.3). It serves as the entry point of the Flutter passkeys package.
2.2. authenticator: The authenticator handles the platform dependent parts and coordinates the native plugins (1.1 & 1.2). On the other side, it communicates with the relying_party_server (2.3).
2.3. relying_party_server: The relying_party_server defines an interface that an implementation of a relying party server should comply to. It is used by the authenticator (2.2) to receive a challenge and validate the response. During initialization it receives an implemented relying party server.
2.4. Specific implementation: The Specific implementation implements the relying_party_server (2.3) in Flutter and communicates with the Relying party server (3).
3. Relying party server: The Relying party server is responsible for managing passkeys. It generates the challenge, verifies the validity of the frontend’s response, and manages the users and passkeys.
As depicted in the chart, it’s possible to add more native plugins. Moreover, you can use any specific implementation and relying party server. For reference, in the example folder, we provide a specific implementation for Corbado and use Corbado as relying party server.
The great benefit of this modular architecture is that it allows to directly use passkeys out-of-the-box (e.g. with Corbado as passkey backend provider), while also giving the flexibility to integrate your own passkey infrastructure instead.
For this tutorial, we just make use of the example project in /examples, that you can clone to your machine with the following command.
Go to the example directory:
In the following, we explain selected important parts of the code.
For simplicity, a single stateful widget is created which represents a login, signup and logged-in page dependent on the state of the application.
Upon initialization,an instance of PasskeyAuth is created,which handles the authentication process and receives SharedRelyingPartyServer as implementation in its constructor. In step 5.2, we will show how to replace the current implementation with your own passkey infrastructure.
That's it concerning the UI of the Flutter application.
When the user clicks on the login / sign up button, the respective action is performed, and the widget is updated once the result is available:
When you cloned the GitHub repo, make sure that you are now in the right directory to run the example app:
- For Android: flutter-passkeys/packages/passkeys/ passkeys/example/android
- For iOS: flutter-passkeys/packages/passkeys/ passkeys/example/ios
Also, make sure that you have a running virtual device or a physical device connected to your machine.
Now, you are fully set and you can start signing up with your first passkey in the example by running the following command:
Note that you share a relying party server with other Flutter developers for this example. Its user table is flushed every day. We have built the example this way to make it very simple to set up. For an example, this works totally fine, but if you want to build your own app you need your own relying party server. Moreover, you cannot run the example on physical iOS devices but only on a simulator (for Android, physical devices + emulators work).
Note: If you just want to quickly try out passkeys in your Flutter app, you don't need to modify anyhting, because the previous example comes with a pre-configured relying party server that can serves passkeys without any additional setup.
The following parts only describe the settings you need to make if you have your own relying party server up and running and want to let your Flutter apps (with the passkeys package) communicate with your relying party server. Setting up your own relying party server from scratch is something that goes beyond the scope of this tutorial. There are several other resources that you can use. Moreover, we plan to publish a separate blog post for setting up a relying party server.
To make your relying party server ready for communication with your Flutter app, you need to obtain some platform-specific information that you need later and also need to modify some platform-specific settings.
You will need your package name (e.g. com.passkeys.example) and your SHA-256 fingerprint (e.g.54:4C:94:2C:E9:...).
You can obtain the SHA-256 fingerprint of your debug signing certificate by executing the following command:
macOS / Linux:
Windows:

This information is needed for step 5.1
If you encounter any issues, please have a look at Troubleshooting.
You need to establish trust between your iOS app and the relying party server. Your app will be identified through your Application Identifier Prefix (e.g. 9RF9KY77B2) and your Bundle Identifier (e.g. com.passkeys.example). You need an Apple developer account to set up both. If you haven't got one yet, set up a new account.
Note: When creating your Bundle Identifier, make sure that the "Associated Domains" capability is enabled.

The Application Identifier Prefix can be obtained by going to your Apple Developer Certificates, Identifier & Profiles associated with your Apple Developer account.
Open your app's code in Xcode now. In "Runner -> Signing & Capabilities" enter your Application Identifier Prefix and your Bundle Identifier.
To set up your iOS app, you will need your Application Identifier Prefix and your Bundle Identifier that we set up in step 4.2.1.
Afterwards, you need to host an apple-app-site-association file on your relying party server on the given relying party ID https://{ your-relying-party-ID }}/.well-known/apple-app-site-association. This file will by downloaded by iOS when you install your app. To tell iOS where to look for the file, we need the next step in our setup.
In your Xcode workspace, you need to configure the following settings: In "Signing & Capabilities" tab, add the "Associated Domains" capability and add the following domain: webcredentials:{ your-relying-party-ID }. Now, iOS knows where to download the apple-app-site-association file from.
If you forget about this step, the example will show you an error message like “Your app is not associated with your relying party server. You have to add...”.
Your configuration inside Xcode should look similar to what is shown in the screenshot below (you will have a different Bundle Identifier and Associated Domain).

Next, we will explore the options for customizing your passkey experience and show you how to switch to your own passkey infrastructure provider.
Each passkey is bound to a certain domain, the so-called relying party ID. If you use the example, passkeys are bound to a shared Corbado subdomain at https://pro-6244024196016258271.frontendapi.corbado.io.
Of course, you can also use your own domain for passkey binding. To do so, you need to verify that both the native app and domain belong to you. Therefore, you need to host the configuration file on your domain:
- Android: assetlinks.json at https://your-domain.com/.well-known/assetlinks.json
- iOS: apple-app-site-association at https://your-domain.com/.well-known/apple-app-site-association
To create these files on your own, you can copy the file structure from the shared Corbado subomain above. Then, you need to update the respective relying party ID / specific domain in the file.
- Android: Update package_name and sha256_cert_fingerprints with the information from step 4.1 in assetlinks.json
- iOS: Update webcredentials.app with the information from step 4.2.3 in apple-app-site-association
In the case of Android, for the assetlinks.json file, you can also use Android Studio’s Digital Asset Links File Generator to generate the file. You find it under “Tools > App Links Assistant > Open Digital Asset Links File Generator“ in Android Studio.



If you want to build an application that is entirely hosted and maintained by you, you can do so by bringing your own passkeys infrastructure, while still using the Flutter passkeys package for easy integration.
Therefore, you need to create a specific implementation in Flutter which implements the relying_party_server (2.3) interface:
As seen in step 2.1, when initializing the authenticator (2.2), a relying_party_server (2.3) object is expected as parameter. In the example, CorbadoPasskeyBackend is just the Corbado implementation of the relying_party_server (2.3) interface. Therefore, if you want to use your own passkey infrastructure, you must create your own implementation of this interface and instead of CorbadoPasskeyBackend, you will use an instance of your own implementation as parameter.
Hint: Look at the CorbadoPasskeyBackend class for orientation on how to create your custom relying_party_server (2.3) implementation.
If you’ve executed one of the commands in step 2.1.1 to view your SHA-256 fingerprint and you see an error message like the following “SHA1withRSA (weak)”:

Then, you need to create a new Android keystore with a stronger algorithm. You can do so with one of the following commands:
- macOS / Linux:
- Windows:
If you run the application in an emulator and it says that you can't create a passkey, you have to log into your Google account and properly set up a screen lock or biometrics on the device.
First, to log into your Google account, open settings, click on the icon in the top right and then on "Sign in to your Google Account".

Secondly, to set up the screen lock, open the settings, search for security settings and add a PIN as well as a fingerprint as shown below (PIN is required for fingerprint):

So far, we've seen how to set up passkey authentication in a Flutter app with the passkeys package.
We now want to quickly go through the most common use cases for Flutter developers when using passkeys. We will try to show how this package can help with each use case. While use case 1 is a very basic one (exploration only), use case 2 and use case 3 are show cases in which real apps are built. Use case 4 and use case 5 are more advanced solutions that try to help you with building more complicated apps.
You just want to see passkeys in action in a Flutter app? Then the example of this package is the right point for you to start. There is no configuration required and you can go through sign up and login flows on your emulator.
Note:
- You share a relying party server with other Flutter developers. Its user table is flushed every day. We have built the example this way to make it very simple to set up. For an example, this works totally fine, but if you want to build your own app you need your own relying party server.
- You cannot run the example on physical iOS devices but only on a simulator (for Android, physical devices + emulators work).
If you already have a relying party server, you just need to tell this package how to interact with it. This is done by implementing the relying_party_server (2.3) interface. Use that implementation to initialize the PasskeyAuth object.
The code below gives an idea on how you can connect your own relying party server by implementing RelyingPartyServer.
To use passkeys in your own app, you need a relying party server. If you don't want to build one, you can use already existing solutions. To allow this package to integrate with a relying party server, the RelyingPartyServer interface must be implemented for that particular server.
Corbado lets you set up a relying party server. This package also comes with a ready to use implementation for the RelyingPartyServer interface that connects to your relying party server. To save time and effort, you can use Corbado as a relying party server. Find an example how to do this including a step-by-step guide here.
You can use every other SaaS provider that allows you to set up a relying party server though. All you need to do is implement the RelyingPartyServer interface.
While this package allows you to use passkeys for authentication, a few challenges remain unsolved, e.g.:
- App users don't want to login every time they open an app, so how can we keep them logged in?
- Not every device supports passkeys yet, how can we provide an alternative login for these users?
- A user might have used your app on another device and now tries to login on a new device. His passkey is still on that old device, so how can we log him in on the new device?
Solving these challenges goes beyond the scope of this package.
As a solution you can create your own authentication SDK that builds on top of the passkeys package. This can make sense if you want to build your own authentication backend.
Alternatively, you can save time and use our solution for that problem: corbado_auth. This is a separate flutter package that builds on top of the passkeys package to provide solutions for the above challenges. For more information, check out its documentation and examples.
Firebase is a great platform that helps you with building your app. We are currently working on a solution that enables this package to be used together with Firebase Auth.
In this tutorial, we showed how to add passkey authentication to a Flutter app using the passkeys package. The package was designed with flexibility in mind, so you can get started fast with Corbado, but also have the option to host everything yourself and only make use of the simple passkey integration.
While the Flutter passkeys package will always remain a clean modular package just for passkey signup and login, the corbado_auth package will add features that can help during the development of more complex apps. Examples of these feature are:
- magic links / OTP as fallback (when an existing user has not set up a passkey on a new device)
- keep a user logged in even when he closes the app
- cross-sync passkeys with a web app
In an upcoming tutorial, we’ll explain in detail how to set up these features with the corbado_auth package in your Flutter app.
Regarding the passkeys package itself, it's planned to
- add more platform support, e.g. for web, macOS or Windows apps.
- more tooling to develop and test your flutter integration (e.g. a hosted open-source relying party server)
Enjoyed this read?
Stay up to date with the latest news, strategies and insights about passkeys sent straight to your inbox!