Skip to main content

FIDO2/Passkey Example Apps

Not for production use

These applications are provided for demonstration and learning purposes only. They are not hardened, audited, or supported for use in production environments. Do not deploy them as-is in a live system.

This guide shows you how to get the Nevis FIDO2 mobile example applications up and running against your Authentication Cloud instance.

Nevis provides two open-source example applications (one for Android and one for iOS) that demonstrate end-to-end FIDO2 passkey registration and authentication. Both are available as public repositories on GitHub and are intended as a practical starting point for development teams building native mobile passkey authentication into their own applications.

Nevis ID compatibility

The example apps are designed to work with Nevis Authentication Cloud as the backend. The underlying FIDO2 concepts (passkey registration, assertion, and the WebAuthn challenge-response flow) apply equally to a Nevis ID deployment. However, the required server-side configuration in Nevis ID is not provided out of the box and requires additional setup.

What you need

Before you start, ensure you have the following ready:

  1. A running Nevis Authentication Cloud instance.
  2. An access key for your Authentication Cloud instance.
  3. The example application sources cloned from GitHub (see below).

Your development environment must meet the following requirements:

RequirementMinimum version
Android9 (API level 28)
JDK17
Android StudioMeerkat Feature Drop 2024.3.2

Getting the example application

Clone the repository for the platform you want to work with:

git clone https://github.com/nevissecurity/nevis-mobile-authentication-fido2-example-android.git

The Android app uses Android Credential Manager to create and assert passkeys. The UI is built with Jetpack Compose and follows a Clean Architecture pattern.

In addition to the standard flows, the Android app supports passkey autofill: the Credential Manager autofill integration surfaces available passkeys in the keyboard bar or a bottom sheet as the user taps a username field.

Physical device required

Passkey APIs require Google Play Services. Use a physical device, or an emulator with Google Play Services and a Google account signed in.

Configure and run

Follow the setup steps in the README.md of the cloned repository. In summary:

  1. In the project root, create or edit local.properties and set the hostname of your Authentication Cloud instance and your access key:

    HOST_NAME=<your-instance>.mauth.nevis.cloud
    BACKEND_ACCESS_TOKEN=<your-access-key>
  2. Open the project in Android Studio and run it on a physical device.

Production architecture for native apps

The example apps embed the access key directly to keep setup self-contained. This is intentional for demo purposes only. The Authentication Cloud REST API is designed exclusively for server-to-server calls: access keys carry unrestricted access to your instance and must never be stored on user devices or included in distributed app packages.

In a production native app integration, the mobile app does not call the Authentication Cloud API directly. The recommended architecture is:

  1. The mobile app sends a request to your own application backend.
  2. Your backend holds the access key, calls the Authentication Cloud API, and receives the challenge or credential options in response.
  3. Your backend forwards these options to the mobile app.
  4. The app uses the platform passkey APIs to create or assert the credential and sends the result back to your backend.
  5. Your backend forwards the credential to Authentication Cloud for verification and returns the outcome to the app.

This keeps the access key server-side and gives you full control over authentication policy, logging, and session management. The FIDO2 indirect communication flows document this pattern in detail:

Authentication Cloud also supports direct communication flows where the client communicates with Authentication Cloud without an intermediate backend. This pattern is primarily designed for web and JavaScript integrations; for native mobile apps the indirect pattern is strongly preferred, as direct communication would require placing the access key on the device.

Demo scenarios

Both apps expose the following scenarios from a single main screen.

Registration

  1. Enter a username and tap Register.
  2. The platform passkey creation sheet appears. Authenticate with biometrics or your device PIN to confirm.
  3. The passkey is registered with your Authentication Cloud instance and stored in the platform credential store (Google account on Android, iCloud Keychain on iOS).

Attempting to register the same username a second time may result in an error. The exact behavior depends on the credential manager: Apple's Passwords app prevents creating a second passkey for the same relying party, while third-party managers such as 1Password may allow it. The server may also return an error if the username is already enrolled. The app demonstrates this error handling explicitly.

Authentication

Username-based authentication:

  1. Enter a previously registered username and tap Authenticate.
  2. The platform passkey selection sheet appears. Authenticate with biometrics or your device PIN.
  3. The app displays the resulting access token claims on success, or an error if no passkey is found for that username.

Usernameless authentication (discoverable credential flow):

  1. Leave the username field blank and tap Authenticate.
  2. The system presents a list of all available passkeys for the relying party. Select one and authenticate.
  3. No prior knowledge of the username is required on the user side.

iOS only: keyboard-assisted authentication:

When the username field is focused, iOS surfaces registered passkeys directly in the QuickType bar above the keyboard. The user can tap a passkey suggestion to authenticate without navigating to a dedicated flow.

Cross-device usage

The example apps also exercise cross-device scenarios that demonstrate the portable nature of FIDO2 passkeys:

  • Using a mobile passkey in a desktop browser: a passkey registered through the app can be used to authenticate in a desktop browser. The browser displays a QR code which the mobile device scans to complete the assertion. This works on both Android and iOS.

  • Storing a passkey on a different device: a registration can be initiated on one device and completed on a second device by scanning the QR code shown during registration. On Android, set the authenticator attachment to cross-platform before starting the registration. The passkey is stored on the device that scanned the QR code.

  • iOS/Android interoperability: a passkey registered via the iOS app can be used for authentication initiated from an Android device (and vice versa), as long as both connect to the same Authentication Cloud backend.

  • iOS iCloud sync: a passkey registered on one iPhone is automatically synchronized to other Apple devices signed in to the same Apple ID via iCloud Keychain, and can be used in Safari on macOS without any additional steps.

Web-based sign-in flow

In addition to native passkey flows, the apps demonstrate a browser-based sign-in flow. In this scenario, the app opens the Authentication Cloud Test & Debug page in a browser, the user authenticates using FIDO2, and the result is passed back to the app as a JWT via a deep link. The app then introspects the token to display the result. In a real integration, the app would open your own FIDO2-enabled login page instead.

The webpage serving the login must redirect back to the app using a custom URL scheme, for example:

<scheme>://success?token=<authorizationToken>
<scheme>://failure?error=<errorMsg>

The two platforms implement this differently:

iOS uses ASWebAuthenticationSession, which is straightforward and well-supported.

Android uses Chrome Custom Tabs. Standard WebView does not support WebAuthn and cannot be used for passkey flows.

Android browser support

Custom Tabs are required for the browser-based sign-in flow on Android. The following browsers are known to support Custom Tabs: Chrome, Brave, Ecosia, Edge, and Vivaldi. Firefox, Firefox Focus, DuckDuckGo, and Samsung Internet do not support the minimized Custom Tab mode. Opera and Mi Browser do not support Custom Tabs at all; when one of these is set as the default browser, a fallback to another Custom-Tab-capable browser is necessary.

Native app association not required for Custom Tabs and ASWebAuthenticationSession

The browser-based sign-in flow used by these example apps (Chrome Custom Tabs on Android, ASWebAuthenticationSession on iOS) does not require the .well-known association files described in Native app association for passkeys. In these flows, WebAuthn is handled entirely by the browser session — the native app only launches the URL and receives the result back via deep link.

This does not apply if you embed a web view directly in your app (Android WebView with Credential Manager, or iOS WKWebView). In that scenario, credential sharing between the web context and the native layer requires the same association files and entitlements described in Native app association for passkeys.

Platform behavior and limitations

Passkey sync and device-binding

Platform passkeys on both iOS and Android are synced credentials by default. It is not possible to create a device-bound passkey that cannot leave the device when using the standard platform authenticator:

  • iOS: when using Apple's built-in credential store, passkeys are synced via iCloud Keychain. Apple does not expose a configuration option to prevent this. If the user has a third-party credential manager configured (for example, 1Password), that manager's sync settings apply instead.
  • Android: passkeys created with the standard Credential Manager are backed up to the user Google account.

If device-binding is a hard requirement, two alternatives are available:

  • Nevis UAF-based mobile authentication: the Nevis Mobile Authentication SDK and the brandable Nevis Access App use the FIDO UAF standard, which creates device-bound credentials that cannot be backed up or synced to another device. See Mobile Authentication (FIDO UAF) Example Apps to explore these capabilities through the SDK example applications.
  • Hardware security key: a key such as a YubiKey used as a cross-platform authenticator is device-bound by design, as the private key never leaves the hardware token.

FIDO2 option support by platform

The apps allow you to configure FIDO2 options before each operation, but the actual behavior depends on the platform authenticator. The table below summarizes how each option is handled:

FIDO2 optioniOS platform passkeyAndroid platform passkeyHardware security key
userVerificationAlways enforced (Face ID / Touch ID / passcode); discouraged is ignoredRespectedRespected (depends on key capabilities)
authenticatorAttachmentIgnored; platform authenticator is always usedRespectedRespected (platform vs. cross-platform)
residentKeyAlways resident and discoverableRespectedFully configurable
attestationNot supported; always returns noneNot supportedSupported (none, indirect, direct)
Syncs across devices✅, via iCloud Keychain✅, via Google account❌; device-bound by design
Can be made device-bound

Further reading