← All projects

flutter

Flutter/Dart client SDK for the aice-auth identity service, covering login, refresh, logout and pluggable secure token storage.

  • Dart 100%
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.
  • TokenBundleaccess_token, token_type, expires_in, refresh_token, optional id_token, optional scope.
  • RegistrationResultaccount_id, handle_id, handle_verification_required.
  • RecoverySession — limited-scope recovery access token.
  • HandleTypephone, email, username.
  • TokenStore interface plus SecureTokenStore (default) and InMemoryTokenStore (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.