Try the Flutter Sample App
The SDK repository ships a sample app in
samples/quickstart
that exercises the full authentication lifecycle end to end: sign-in, sign-up, and sign-out via
the SDK's app-native Flow Execution, on both iOS and Android.
Prerequisites
- Flutter 3.16+
- A running ThunderID instance
- iOS 16+ or Android API 26+
Prepare the Flutter Sample
cd samples/quickstart
# 1. Copy and fill in your environment
cp .env.example .env
# 2. Install dependencies
flutter pub get
Configure the Flutter Sample
This sample uses app-native authentication (Flow Execution API), so only the base URL and application ID are required, no OAuth2 client ID or redirect URIs.
| Variable | Description |
|---|---|
THUNDERID_BASE_URL | Base URL of your ThunderID server (HTTPS) |
THUNDERID_APP_ID | Application UUID from ThunderID console |
THUNDERID_ATTESTATION_ENABLED | true to send a platform attestation token on native flows |
THUNDERID_CLOUD_PROJECT_NUMBER | Google Cloud project number (Android Play Integrity) |
.env is gitignored. Never commit real credentials.
Attestation via Google Play Integrity / Apple App Attest (optional)
Set THUNDERID_ATTESTATION_ENABLED=true to send a platform attestation token on native
sign-in/sign-up. The token is minted natively by the plugin, Apple App Attest on iOS,
Google Play Integrity on Android.
Requirements to test end-to-end:
- Android: set
THUNDERID_CLOUD_PROJECT_NUMBER, and publish the app to a Play Console track linked to that Cloud project (Play Integrity needs a recognized package + signature). - iOS: a physical device and the App Attest capability on the
Runnertarget (adds thecom.apple.developer.devicecheck.appattest-environmententitlement). App Attest does not work in the simulator. - Server: the Team ID and Bundle ID registered on the ThunderID application's
attestation settings must match the ones the app is signed with. ThunderID derives the expected
App ID from
<TeamID>.<BundleID>and rejects a token whose attested App ID differs.
The plugin generates the App Attest challenge locally. ThunderID does not yet bind verification to a server-issued challenge, so this is sufficient to exercise the flow end to end; move to a server-issued challenge once that check lands.
Passkeys (WebAuthn)
Passkey registration/authentication requires the server's passkey relying party (rp.id) to be a
real HTTPS domain, not localhost, 127.0.0.1, or 10.0.2.2. Each platform verifies the calling
app is allowed to use that rp.id differently, and this Flutter sample packages both a native iOS
runner and a native Android app, so both verification mechanisms apply:
iOS, Associated Domains entitlement. ASAuthorizationPlatformPublicKeyCredentialProvider
requires the app to declare a webcredentials:<domain> Associated Domain, backed by a hosted
apple-app-site-association file. Without this, ASAuthorizationController fails immediately with
Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1004.
This sample ships ios/Runner/Runner.entitlements with a placeholder
webcredentials:your-thunderid-domain.example entry, wired into ios/Runner.xcodeproj via
CODE_SIGN_ENTITLEMENTS. To exercise passkeys end-to-end:
- Replace the placeholder domain in
ios/Runner/Runner.entitlementswith the domain your ThunderID server is actually reachable at (must serve valid HTTPS, self-signed certs andlocalhostwill not work). - Host an
apple-app-site-associationfile athttps://<that-domain>/.well-known/apple-app-site-associationdeclaring this app's Team ID and bundle identifier (dev.thunderid.Quickstart) underwebcredentials.apps. - Make sure the server's passkey
rp.idmatches that same domain. - Set a
DEVELOPMENT_TEAMand enable the Associated Domains capability for the Runner target in Xcode (Signing & Capabilities) so the entitlement is actually applied to the build.
Android, Digital Asset Links. Android's Credential Manager requires the domain to serve
https://<domain>/.well-known/assetlinks.json declaring this app's package name and signing
certificate SHA-256 fingerprint(s) under delegate_permission/common.get_login_creds. Without this,
Credential Manager rejects the ceremony.
This sample ships assetlinks.json.example with a placeholder sha256_cert_fingerprints entry for
the app's applicationId (dev.thunderid.flutter_quickstart). To exercise passkeys end-to-end:
- Rename/copy
assetlinks.json.exampletoassetlinks.jsonand replace<YOUR_APP_SIGNING_CERT_SHA256_FINGERPRINT>with the SHA-256 fingerprint of the signing certificate for the build you'll test with (get it viakeytool -list -v -keystore <your.keystore> -alias <alias>or a GradlesigningReport). - Host that file at
https://<your-thunderid-domain>/.well-known/assetlinks.json(same domain as the iOS setup above, and matching the server'srp.id). - No
AndroidManifest.xmlchange is required, Digital Asset Links verification is purely a server-hosted file requirement, no App Links intent filter needed.
Exposing a local ThunderID instance under a real, HTTPS-reachable domain (e.g. via a tunnel) is left to you, this sample only wires up the entitlement/template file/documentation on each platform, not the tunnel itself.
Run
Open the project in your IDE, or open a terminal in the project directory:
# List available devices
flutter devices
# Launch a specific emulator
flutter emulators --launch <emulator_id>
# Run on a specific device
flutter run -d <device_id>
Next Steps
- Accessing Protected APIs: Use ThunderID Flutter SDK access tokens to call your backend APIs