Ace ID Mobile
Ace ID Mobile is the Flutter Android account client in the public aceyash-dev/AceID repository. The identity provider and account APIs remain in the separate aceyash-dev/ace-id backend repository. Product and integration documentation is maintained in aceyash-dev/ace-docs.
Status: Android UI, AppAuth authorization-code flow, secure local credential storage, account API calls, and Android APK CI are present in source. A successful production authorization-code + PKCE round trip must still be verified on a real device before claiming the native sign-in path is production-validated. A passing build or widget test does not prove end-to-end authentication.
Current implementation
- Framework: Flutter.
- Android application ID:
app.tab.aid. - OIDC redirect URI:
app.tab.aid:/oauth2redirect. - Issuer default:
https://identity.ace-base.cc. - Authentication package:
flutter_appauth, using the system browser and Authorization Code + PKCE. - Local credential storage:
flutter_secure_storage. - Native screens: Overview, Security and devices, Connected apps, Account, and subscription/billing information.
- Sign-in UI: Ace ID branding in Satoshi, a light/dark theme toggle with a persisted preference, and Privacy/Terms links. The sign-in screen does not offer a “Continue on the web” button.
- Native API calls: account/profile, subscription and plans, devices, activity logs, connected-app listing, and connected-app revocation.
- Web account controls: password, passkeys, MFA, device/session management, and account deletion open the authenticated Ace ID website.
- CI targets: Android APK only. iOS, Flutter web, desktop builds and store signing are not part of the current workflow.
Keep the application ID and redirect URI aligned with the registered public OIDC client. Changing either requires coordinating the installed app identity and issuer registration.
OIDC configuration
The app reads these compile-time Dart defines:
| Dart define | Default | Purpose |
|---|---|---|
ACEID_ISSUER | https://identity.ace-base.cc | Canonical OIDC issuer |
ACEID_MOBILE_CLIENT_ID | empty | Registered public OIDC client ID |
ACEID_MOBILE_REDIRECT_URI | app.tab.aid:/oauth2redirect | Native authorization callback |
Set the GitHub Actions repository variable ACEID_MOBILE_CLIENT_ID to the registered public client ID. The Android workflow passes it to Flutter during packaging. A public client ID is not a secret. Never embed a client secret, database URL, session secret, or signing key in an APK.
If the variable is empty, native sign-in is unavailable and the app reports that the OIDC client is not configured. There is no “Continue on the web” sign-in fallback. The hosted website remains available for account-security controls that are not implemented natively, but those links are not a replacement sign-in flow. Backend API authorization, token expiry, account ownership, and registered client restrictions remain authoritative. Do not treat locally decoded ID-token fields as authorization to call protected APIs.
Token lifecycle and end-to-end validation
The app stores access, refresh and ID tokens plus the profile snapshot and access-token expiry in platform secure storage. It refreshes expiring access tokens and attempts RP-initiated logout using the ID-token hint and registered post-logout redirect. Local credentials are cleared even if provider logout fails or is cancelled.
Before describing native login as production-ready, test a real Authorization Code + PKCE round trip against a registered public client and verify login, callback, token exchange, issuer/audience/nonce handling, expiry, cancellation, refresh, logout and account switching. The backend CI live-issuer smoke test verifies issuer discovery and endpoint contracts, not the full native flow.
Current screens and API boundaries
The native app calls these existing backend routes:
| Feature | Route |
|---|---|
| Account/profile | /api/account |
| Devices/sessions | /api/account/devices |
| Activity | /api/account/logs |
| Connected apps | GET /api/account/connected-apps |
| Revoke connected app | DELETE /api/account/connected-apps/:grantId |
| Plans | /api/subscription/plans |
| Current subscription | /api/subscription |
| Checkout | /api/subscription/checkout |
The native password, passkey and MFA controls currently open the web account surface rather than implementing those security flows natively. Do not describe them as native capabilities. Keep server-side token expiry, authorization, ownership checks, error handling and rate limits authoritative.
Security requirements
- Use the system browser and Authorization Code + PKCE for native OIDC.
- Use the registered public client ID and exact registered redirect URI. Never include a client secret in the app.
- Keep access and refresh tokens in platform secure storage, not preferences, logs, screenshots, or a general offline cache.
- Refresh tokens or require clean reauthentication when tokens expire; a cached profile must never authorize a protected operation.
- Test issuer logout, local credential clearing and subsequent SSO sign-in separately. Provider logout failure must not prevent local credential clearing.
- Keep API authorization and resource ownership enforcement on the server.
- Treat a build, UI screenshot or widget test as insufficient evidence of a successful live OIDC sign-in.
Build and CI
The canonical workflow is .github/workflows/android.yml. It runs for pushes and pull requests targeting main when mobile/** or the workflow file changes, and supports manual dispatch.
CI runs dependency installation, launcher-icon generation, Dart formatting and analysis, widget tests, Android Gradle wrapper generation, and release packaging. It builds only the armeabi-v7a APK. The workflow uploads it as the ace-id-armeabi-v7a-test-apk artifact for 14 days; it is a temporary workflow artifact, not a versioned GitHub Release. No arm64-v8a, universal, or x86_64 APK is produced by CI.
Build dependencies are declared in mobile/pubspec.yaml. A mobile/pubspec.lock file is not currently tracked, so dependency resolution can change as compatible package releases change. Commit and enforce the application lockfile before treating artifacts as reproducible release inputs.
CI artifacts use the default debug signing setup and are not Play Store release packages. Production signing and store publishing require a separate protected signing configuration.
Remaining milestones
- Commit
mobile/pubspec.lockand verify repeatable builds. - Verify the registered public client ID, exact callback URI, and a real production/staging Authorization Code + PKCE round trip on an Android device.
- Add integration tests for refresh/expiry, issuer logout, account switching and subsequent sign-in.
- Add failure-path coverage for invalid, expired and revoked tokens, authorization failures, and offline mutation prevention.
- Add iOS only when its project, redirect URI registration and CI build are implemented and verified.
Acceptance criteria
- Android package identity and native redirect URI are stable and registered with the issuer.
- A registered public client signs in through Authorization Code + PKCE without a client secret.
- A real issuer round trip passes; an optional or skipped smoke test does not count as live-auth validation.
- Token refresh/expiry, logout and account switching behave predictably and are tested.
- Native management screens call documented APIs with server-enforced authorization and ownership.
- Dependencies are locked and release builds are reproducible.
- Documentation matches the implementation in the
mainbranch ofaceyash-dev/AceIDand themasterbranch ofaceyash-dev/ace-id.