flutter
Flutter/Dart client SDK for the aice-auth identity service, covering login, refresh, logout and pluggable secure token storage.
git@gitlab.com:aice-lab/auth/flutter.git
Latest release
v1.0.1 ·
README
aice-auth (Flutter)
Flutter client SDK for the aice-auth identity service.
This package implements the consumer side of the /auth/login, /auth/register, /auth/register/verify, /auth/refresh, /auth/logout, recovery-OTP (/auth/recover/start, /auth/recover/verify-handle), passkey (/auth/passkey/authenticate*, /auth/credentials/passkey*), and social (/auth/social/{provider}/start, /auth/social/exchange) endpoints defined in the canonical OpenAPI spec. It persists the resulting TokenBundle in flutter_secure_storage so app code can call getAccessToken() without re-authenticating on every cold start.
Status
Stable. v1.0.x covers first-factor handle + credential login, refresh-token rotation, logout, and access-token retrieval. v1.1.0 adds the recovery-OTP flow (recoverStart / recoverVerifyHandle) and LicenseDegradedException — the typed form of the issuer’s 503 license_degraded (degraded-mode contract). v1.2.0 adds account registration, passkey transport (login + registration), and social sign-in (URL builder + code exchange).
License-token verification is deferred for this client (no audited Dart ML-DSA-65 implementation — see docs/license-verification-deferral.md). Clients are backend-delegated: the backend verifies the hybrid token and refuses degraded requests with 503 license_degraded, which this SDK surfaces as LicenseDegradedException. Automatic refresh-on-expiry is deliberately not implemented (refresh() is explicit).
Install
dependencies:
aice_auth:
git:
url: git@gitlab.com:aice-lab/auth/flutter.git
ref: v1.0.0
Pin to a tag (v1.0.0 or later) rather than main. main is unstable between releases.
Quickstart
import 'package:aice_auth/aice_auth.dart';
final client = AiceAuthClient(
issuerBaseUrl: 'https://id.example.com',
audience: 'my-tenant', // sent as X-Aice-Audience on every request
);
final tokens = await client.login(
handle: 'alice@example.org',
handleType: HandleType.email,
credential: 'correct-horse-battery-staple',
);
// Later requests:
final accessToken = await client.getAccessToken();
// When the access token expires, rotate the refresh chain:
await client.refresh();
// On sign-out:
await client.logout();
Flows
Create account (email + password)
final result = await client.register(
handleType: HandleType.email,
handleValue: 'alice@example.org',
password: 'correct-horse-battery-staple',
);
// The issuer auto-dispatches an OTP to the email. Verify it, then login()
// succeeds (until then it rejects with handle_not_verified):
await client.verifyRegistrationOtp(
handleId: result.handleId,
otp: emailedCode,
);
verifyRegistrationOtp({handleId, otp}) submits the code to
POST /auth/register/verify (issuer ≥ 0.7.0). It is unauthenticated by
design — a fresh registrant has no bearer token, because login rejects
unverified handles.
Passkey login
The SDK is transport-only: it never talks to the platform authenticator (no
platform channels). The app drives WebAuthn (e.g. the passkeys package) with
the JSON this SDK relays:
final options = await client.passkeyAuthOptions();
final assertion = /* platform authenticator processes `options` */;
final tokens = await client.passkeyAuthenticate(assertion);
// TokenBundle is persisted, same as login().
Passkey registration (recovery-scoped)
The issuer’s add-passkey endpoints require step-up: a bearer with
scope: auth:recover (or acr: aal2). A plain login token is rejected with
step_up_required. Run it inside a recovery session and pass that token as
the explicit bearer — these methods never read the token store:
final recoveryId = await client.recoverStart(
handleType: HandleType.email,
handleValue: 'alice@example.org',
);
final session = await client.recoverVerifyHandle(
recoveryId: recoveryId,
otp: emailedCode,
);
final options =
await client.passkeyRegisterOptions(bearer: session.accessToken);
final attestation = /* platform authenticator processes `options` */;
final passkeyId = await client.passkeyRegister(
attestation,
bearer: session.accessToken,
);
Social sign-in (Google / Apple)
final url = client.socialStartUrl(
'google',
returnTo: 'myapp://auth/social/callback',
);
// The client's audience rides along as ?audience=… (browser navigations
// cannot set the X-Aice-Audience header).
// Open `url` in an external browser / custom tab. The issuer redirects back
// to returnTo?code=… (returnTo must exactly match the tenant allowlist).
final tokens = await client.socialExchange(codeFromDeepLink);
// TokenBundle is persisted, same as login().
Example
See example/ for a single-screen Flutter app that drives login → getAccessToken → refresh → logout against a local aice-auth issuer. Run instructions are in the example’s own README.
Public API
Everything exported from package:aice_auth/aice_auth.dart is part of the public API:
AiceAuthClient— the entry-point.TokenBundle—access_token,token_type,expires_in,refresh_token, optionalid_token, optionalscope.RegistrationResult—account_id,handle_id,handle_verification_required.RecoverySession— limited-scope recovery access token.HandleType—phone,email,username.TokenStoreinterface plusSecureTokenStore(default) andInMemoryTokenStore(tests).AiceAuthException— typed exception thrown on auth failures.
Anything imported from package:aice_auth/src/... is internal and may change without notice between minor versions.
Testing
dart pub get
dart analyze --fatal-infos
dart test
For coverage:
dart test --coverage=coverage
dart pub global activate coverage
dart pub global run coverage:format_coverage \
--lcov --in=coverage --out=coverage/lcov.info \
--packages=.dart_tool/package_config.json --report-on=lib
lcov --summary coverage/lcov.info
License
FSL-1.1-Apache-2.0. See LICENSE.FAQ.md for the practical effect.
Contributing
See CONTRIBUTING.md. TDD is mandatory; every code change ships with a corresponding test commit.
Reporting security issues
See SECURITY.md. Do not file public issues for vulnerabilities.
This is a snapshot generated from GitLab. For the live README, see the project page.