Integrate passkeys into your Flutter apps with our open-source passkeys package
Implementation

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. Flutter passkeys

      1.1 General passkeys flow

      1.2 Flutter passkeys package architecture

2. Create Flutter application

      2.1 Set up UI

      2.2 Perform authentication

3. Run the example app

4. (Optional) Configure your relying party server

      4.1 Set up Android app

      4.2 Set up iOS app

5. (Optional) Customization

      5.1 Bind passkeys to specific domain

      5.2 Integrate your own passkeys backend

6. Troubleshooting

      6.1 Weak SHA-1 algorithm for Android keystore

      6.2 Android screen lock not set up on virtual Android device

7. Next steps

      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

8. Conclusion

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:

Flutter Passkey Flow Example GIF

1. Flutter passkeys

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).

1.1. General passkeys flow

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.

1.2. Flutter passkeys package architecture

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.

Corbado Flutter Passkey Architecture

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.

2. Create a Flutter application

For this tutorial, we just make use of the example project in /examples, that you can clone to your machine with the following command.

git clone https://github.com/corbado/flutter-passkeys

Go to the example directory:

cd packages/passkeys/passkeys/example

In the following, we explain selected important parts of the code.

2.1 Setup UI

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.

2.2 Perform passkey authentication

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:

3. Run the example app

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:

flutter run lib/main.dart

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).

4. Configure your relying party server

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.

4.1 Set up Android app

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:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

Windows:

keytool -list -v -keystore "\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
Android Debug Keystore

This information is needed for step 5.1

If you encounter any issues, please have a look at Troubleshooting.

4.2 Set up iOS app

4.2.1 Create an iOS app and configure the example in XCode

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.

Apple Bundle ID

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.

4.2.2 Set up iOS Application Identifier Prefix and 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.

4.2.3 Configure your iOS project

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).

Apple Signing Capabilities

5. (Optional) Customizations

Next, we will explore the options for customizing your passkey experience and show you how to switch to your own passkey infrastructure provider.

5.1 Bind passkeys to specific domain

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.

Android Studio App Linkin Assistant
Android Studio App Linkin Assistant Details
Declare Website Association

5.2 (Optional) Integrate your own passkeys infrastructure

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.

6. Troubleshooting

6.1 Weak SHA-1 algorithm for Android keystore

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)”:

Weak SHA-1 Algorithm

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:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
  • Windows:
keytool -list -v -keystore "\.android\debug.keystore”-alias androiddebugkey -storepass android -keypass android

6.2 Android screen lock not set up on virtual Android device

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".

Troubleshooting Sign in to 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):

Troubleshooting No Screen Lock

7. Next steps

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.

7.1 Prototype with passkeys

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).

7.2 Use passkeys for your app with an existing relying party server

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.

7.3 Use passkeys for your app but you don't want to build your own relying party server

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.

7.4 Build an app that supports more advanced use cases with passkeys

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.

7.5 Use Firebase together with passkeys

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.

8. Conclusion

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!